Google 계정 로그인 연동 추가
This commit is contained in:
@@ -131,6 +131,11 @@ const mapAdminMemberRow = (row) => {
|
||||
* @property {boolean} isAdmin - 관리자 여부
|
||||
* @property {'owner' | 'admin' | 'vip' | 'member'} role - 권한 코드
|
||||
* @property {number} sessionVersion - 세션 무효화 버전
|
||||
* @property {string} googleSub - Google 사용자 고유 ID
|
||||
* @property {string} googleEmail - Google 계정 이메일
|
||||
* @property {string} googleName - Google 계정 이름
|
||||
* @property {string} googleAvatarUrl - Google 계정 프로필 이미지
|
||||
* @property {string | null} googleLinkedAt - Google 계정 연결 시각
|
||||
* @property {string} createdAt - 생성 시각(ISO)
|
||||
* @property {string} updatedAt - 수정 시각(ISO)
|
||||
* @property {string | null} lastSeenAt - 최근 접속 시각(ISO)
|
||||
@@ -171,6 +176,11 @@ export const getUserByEmail = async (email) => {
|
||||
is_admin AS "isAdmin",
|
||||
user_role AS "role",
|
||||
session_version AS "sessionVersion",
|
||||
google_sub AS "googleSub",
|
||||
google_email AS "googleEmail",
|
||||
google_name AS "googleName",
|
||||
google_avatar_url AS "googleAvatarUrl",
|
||||
google_linked_at AS "googleLinkedAt",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
last_seen_at AS "lastSeenAt",
|
||||
@@ -185,6 +195,42 @@ export const getUserByEmail = async (email) => {
|
||||
return rows?.[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Google 사용자 고유 ID로 회원을 조회한다.
|
||||
* @param {string} googleSub - Google 사용자 고유 ID
|
||||
* @returns {Promise<MemberUser | null>} 회원
|
||||
*/
|
||||
export const getUserByGoogleSub = async (googleSub) => {
|
||||
const sql = requireSql()
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
password_hash AS "passwordHash",
|
||||
avatar_url AS "avatarUrl",
|
||||
is_admin AS "isAdmin",
|
||||
user_role AS "role",
|
||||
session_version AS "sessionVersion",
|
||||
google_sub AS "googleSub",
|
||||
google_email AS "googleEmail",
|
||||
google_name AS "googleName",
|
||||
google_avatar_url AS "googleAvatarUrl",
|
||||
google_linked_at AS "googleLinkedAt",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
last_seen_at AS "lastSeenAt",
|
||||
last_seen_ip AS "lastSeenIp",
|
||||
previous_last_seen_at AS "previousLastSeenAt",
|
||||
previous_last_seen_ip AS "previousLastSeenIp"
|
||||
FROM users
|
||||
WHERE google_sub = ${googleSub}
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
return rows?.[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* ID로 회원 조회
|
||||
* @param {string} id - 사용자 ID
|
||||
@@ -200,6 +246,11 @@ export const getUserById = async (id) => {
|
||||
avatar_url AS "avatarUrl",
|
||||
is_admin AS "isAdmin",
|
||||
user_role AS "role",
|
||||
google_sub AS "googleSub",
|
||||
google_email AS "googleEmail",
|
||||
google_name AS "googleName",
|
||||
google_avatar_url AS "googleAvatarUrl",
|
||||
google_linked_at AS "googleLinkedAt",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
last_seen_at AS "lastSeenAt",
|
||||
@@ -229,6 +280,11 @@ export const getUserProfileById = async (id) => {
|
||||
users.avatar_url AS "avatarUrl",
|
||||
users.is_admin AS "isAdmin",
|
||||
users.user_role AS "role",
|
||||
users.google_sub AS "googleSub",
|
||||
users.google_email AS "googleEmail",
|
||||
users.google_name AS "googleName",
|
||||
users.google_avatar_url AS "googleAvatarUrl",
|
||||
users.google_linked_at AS "googleLinkedAt",
|
||||
users.created_at AS "createdAt",
|
||||
users.updated_at AS "updatedAt",
|
||||
users.last_seen_at AS "lastSeenAt",
|
||||
@@ -262,6 +318,12 @@ export const getUserByIdWithPassword = async (id) => {
|
||||
avatar_url AS "avatarUrl",
|
||||
is_admin AS "isAdmin",
|
||||
user_role AS "role",
|
||||
session_version AS "sessionVersion",
|
||||
google_sub AS "googleSub",
|
||||
google_email AS "googleEmail",
|
||||
google_name AS "googleName",
|
||||
google_avatar_url AS "googleAvatarUrl",
|
||||
google_linked_at AS "googleLinkedAt",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
last_seen_at AS "lastSeenAt",
|
||||
@@ -336,6 +398,135 @@ export const createUser = async (input) => {
|
||||
return created
|
||||
}
|
||||
|
||||
/**
|
||||
* Google 계정 기반 회원을 생성한다.
|
||||
* @param {{ username: string, email: string, passwordHash: string, avatarUrl: string, googleSub: string, googleEmail: string, googleName: string, googleAvatarUrl: string }} input - 입력
|
||||
* @returns {Promise<Omit<MemberUser, 'passwordHash'>>} 생성된 회원
|
||||
*/
|
||||
export const createGoogleUser = async (input) => {
|
||||
const sql = requireSql()
|
||||
|
||||
const rows = await sql.begin(async (tx) => {
|
||||
await tx`LOCK TABLE users IN SHARE ROW EXCLUSIVE MODE`
|
||||
const privilegedRows = await tx`
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE user_role = ANY(${PRIVILEGED_ROLES})
|
||||
) AS "hasPrivilegedUsers"
|
||||
`
|
||||
const shouldCreateOwner = !Boolean(privilegedRows?.[0]?.hasPrivilegedUsers)
|
||||
|
||||
return tx`
|
||||
INSERT INTO users (
|
||||
username,
|
||||
email,
|
||||
password_hash,
|
||||
avatar_url,
|
||||
is_admin,
|
||||
user_role,
|
||||
google_sub,
|
||||
google_email,
|
||||
google_name,
|
||||
google_avatar_url,
|
||||
google_linked_at
|
||||
)
|
||||
VALUES (
|
||||
${input.username},
|
||||
${input.email},
|
||||
${input.passwordHash},
|
||||
${input.avatarUrl},
|
||||
${shouldCreateOwner},
|
||||
CASE
|
||||
WHEN ${shouldCreateOwner} THEN ${MEMBER_ROLE.OWNER}
|
||||
ELSE ${MEMBER_ROLE.MEMBER}
|
||||
END,
|
||||
${input.googleSub},
|
||||
${input.googleEmail},
|
||||
${input.googleName},
|
||||
${input.googleAvatarUrl},
|
||||
now()
|
||||
)
|
||||
RETURNING
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
avatar_url AS "avatarUrl",
|
||||
is_admin AS "isAdmin",
|
||||
user_role AS "role",
|
||||
session_version AS "sessionVersion",
|
||||
google_sub AS "googleSub",
|
||||
google_email AS "googleEmail",
|
||||
google_name AS "googleName",
|
||||
google_avatar_url AS "googleAvatarUrl",
|
||||
google_linked_at AS "googleLinkedAt",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
last_seen_at AS "lastSeenAt",
|
||||
last_seen_ip AS "lastSeenIp",
|
||||
previous_last_seen_at AS "previousLastSeenAt",
|
||||
previous_last_seen_ip AS "previousLastSeenIp"
|
||||
`
|
||||
})
|
||||
|
||||
const created = rows?.[0]
|
||||
if (!created) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: 'Google 회원 생성에 실패했습니다.'
|
||||
})
|
||||
}
|
||||
|
||||
return created
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 회원에 Google 계정을 연결한다.
|
||||
* @param {{ userId: string, googleSub: string, googleEmail: string, googleName: string, googleAvatarUrl: string }} input - 연결 정보
|
||||
* @returns {Promise<MemberUser | null>} 수정된 회원
|
||||
*/
|
||||
export const linkGoogleIdentityToUser = async (input) => {
|
||||
const sql = requireSql()
|
||||
|
||||
const rows = await sql`
|
||||
UPDATE users
|
||||
SET
|
||||
google_sub = ${input.googleSub},
|
||||
google_email = ${input.googleEmail},
|
||||
google_name = ${input.googleName},
|
||||
google_avatar_url = ${input.googleAvatarUrl},
|
||||
google_linked_at = now(),
|
||||
avatar_url = CASE
|
||||
WHEN avatar_url = '' THEN ${input.googleAvatarUrl}
|
||||
ELSE avatar_url
|
||||
END,
|
||||
updated_at = now()
|
||||
WHERE id = ${input.userId}
|
||||
RETURNING
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
password_hash AS "passwordHash",
|
||||
avatar_url AS "avatarUrl",
|
||||
is_admin AS "isAdmin",
|
||||
user_role AS "role",
|
||||
session_version AS "sessionVersion",
|
||||
google_sub AS "googleSub",
|
||||
google_email AS "googleEmail",
|
||||
google_name AS "googleName",
|
||||
google_avatar_url AS "googleAvatarUrl",
|
||||
google_linked_at AS "googleLinkedAt",
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
last_seen_at AS "lastSeenAt",
|
||||
last_seen_ip AS "lastSeenIp",
|
||||
previous_last_seen_at AS "previousLastSeenAt",
|
||||
previous_last_seen_ip AS "previousLastSeenIp"
|
||||
`
|
||||
|
||||
return rows?.[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 권한이 없을 때 환경 변수 기반 owner 계정을 생성하거나 기존 회원을 승격한다.
|
||||
* @param {{ username: string, email: string, passwordHash: string }} input - 입력
|
||||
|
||||
Reference in New Issue
Block a user