1
This commit is contained in:
156
server-node/src/repositories/authIdentityRepository.ts
Normal file
156
server-node/src/repositories/authIdentityRepository.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import type { QueryResultRow } from 'pg';
|
||||
|
||||
import type { AppDatabase } from '../db.js';
|
||||
|
||||
export type AuthIdentityProvider = 'wechat';
|
||||
|
||||
export type AuthIdentityRecord = {
|
||||
id: string;
|
||||
userId: string;
|
||||
provider: AuthIdentityProvider;
|
||||
providerUid: string;
|
||||
providerUnionId: string | null;
|
||||
displayName: string | null;
|
||||
avatarUrl: string | null;
|
||||
isVerified: boolean;
|
||||
metaJson: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
type AuthIdentityRow = QueryResultRow & {
|
||||
id: string;
|
||||
user_id: string;
|
||||
provider: AuthIdentityProvider;
|
||||
provider_uid: string;
|
||||
provider_unionid: string | null;
|
||||
display_name: string | null;
|
||||
avatar_url: string | null;
|
||||
is_verified: boolean;
|
||||
meta_json: Record<string, unknown> | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
function toAuthIdentityRecord(
|
||||
row: AuthIdentityRow | undefined,
|
||||
): AuthIdentityRecord | null {
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
userId: row.user_id,
|
||||
provider: row.provider,
|
||||
providerUid: row.provider_uid,
|
||||
providerUnionId: row.provider_unionid,
|
||||
displayName: row.display_name,
|
||||
avatarUrl: row.avatar_url,
|
||||
isVerified: row.is_verified,
|
||||
metaJson: row.meta_json,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
export type CreateWechatIdentityInput = {
|
||||
userId: string;
|
||||
providerUid: string;
|
||||
providerUnionId: string | null;
|
||||
displayName: string | null;
|
||||
avatarUrl: string | null;
|
||||
metaJson?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
export class AuthIdentityRepository {
|
||||
constructor(private readonly db: AppDatabase) {}
|
||||
|
||||
async findWechatIdentityByProfile(params: {
|
||||
providerUid: string;
|
||||
providerUnionId: string | null;
|
||||
}) {
|
||||
const result = params.providerUnionId
|
||||
? await this.db.query<AuthIdentityRow>(
|
||||
`SELECT id, user_id, provider, provider_uid, provider_unionid, display_name, avatar_url, is_verified, meta_json, created_at, updated_at
|
||||
FROM auth_identities
|
||||
WHERE provider = 'wechat'
|
||||
AND (provider_unionid = $1 OR provider_uid = $2)
|
||||
ORDER BY
|
||||
CASE WHEN provider_unionid = $1 THEN 0 ELSE 1 END
|
||||
LIMIT 1`,
|
||||
[params.providerUnionId, params.providerUid],
|
||||
)
|
||||
: await this.db.query<AuthIdentityRow>(
|
||||
`SELECT id, user_id, provider, provider_uid, provider_unionid, display_name, avatar_url, is_verified, meta_json, created_at, updated_at
|
||||
FROM auth_identities
|
||||
WHERE provider = 'wechat'
|
||||
AND provider_uid = $1
|
||||
LIMIT 1`,
|
||||
[params.providerUid],
|
||||
);
|
||||
|
||||
return toAuthIdentityRecord(result.rows[0]);
|
||||
}
|
||||
|
||||
async listByUserId(userId: string) {
|
||||
const result = await this.db.query<AuthIdentityRow>(
|
||||
`SELECT id, user_id, provider, provider_uid, provider_unionid, display_name, avatar_url, is_verified, meta_json, created_at, updated_at
|
||||
FROM auth_identities
|
||||
WHERE user_id = $1
|
||||
ORDER BY provider, created_at`,
|
||||
[userId],
|
||||
);
|
||||
|
||||
return result.rows
|
||||
.map((row) => toAuthIdentityRecord(row))
|
||||
.filter((row): row is AuthIdentityRecord => Boolean(row));
|
||||
}
|
||||
|
||||
async createWechatIdentity(input: CreateWechatIdentityInput) {
|
||||
const now = new Date().toISOString();
|
||||
const identityId = `authi_${crypto.randomBytes(16).toString('hex')}`;
|
||||
const result = await this.db.query<AuthIdentityRow>(
|
||||
`INSERT INTO auth_identities (
|
||||
id,
|
||||
user_id,
|
||||
provider,
|
||||
provider_uid,
|
||||
provider_unionid,
|
||||
display_name,
|
||||
avatar_url,
|
||||
is_verified,
|
||||
meta_json,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES ($1, $2, 'wechat', $3, $4, $5, $6, TRUE, $7, $8, $9)
|
||||
RETURNING id, user_id, provider, provider_uid, provider_unionid, display_name, avatar_url, is_verified, meta_json, created_at, updated_at`,
|
||||
[
|
||||
identityId,
|
||||
input.userId,
|
||||
input.providerUid,
|
||||
input.providerUnionId,
|
||||
input.displayName,
|
||||
input.avatarUrl,
|
||||
input.metaJson ?? null,
|
||||
now,
|
||||
now,
|
||||
],
|
||||
);
|
||||
|
||||
return toAuthIdentityRecord(result.rows[0]);
|
||||
}
|
||||
|
||||
async moveWechatIdentitiesToUser(sourceUserId: string, targetUserId: string) {
|
||||
await this.db.query(
|
||||
`UPDATE auth_identities
|
||||
SET user_id = $1, updated_at = $2
|
||||
WHERE user_id = $3
|
||||
AND provider = 'wechat'`,
|
||||
[targetUserId, new Date().toISOString(), sourceUserId],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user