diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-03-26 00:37:41 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-03-26 00:37:41 +0000 |
| commit | e0dfb55c5457aec489fc084c4567e791b4c65eb1 (patch) | |
| tree | 68543a65d88f5afb3a0202925804103daa91bc6f /lib/users/service.ts | |
3/25 까지의 대표님 작업사항
Diffstat (limited to 'lib/users/service.ts')
| -rw-r--r-- | lib/users/service.ts | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/lib/users/service.ts b/lib/users/service.ts new file mode 100644 index 00000000..ae97beed --- /dev/null +++ b/lib/users/service.ts @@ -0,0 +1,413 @@ +// lib/users/service.ts +"use server"; + +import { Otp } from '@/types/user'; +import { getAllUsers, createUser, getUserById, updateUser, deleteUser, getUserByEmail, createOtp,getOtpByEmailAndToken, updateOtp, findOtpByEmail ,getOtpByEmailAndCode, findAllRoles, getRoleAssignedUsers} from './repository'; +import logger from '@/lib/logger'; +import { Role, userRoles, users, userView, type User } from '@/db/schema/users'; +import { saveDocument } from '../storage'; +import { GetUsersSchema } from '../admin-users/validations'; +import { revalidateTag, unstable_cache, unstable_noStore } from 'next/cache'; +import { filterColumns } from '../filter-columns'; +import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq } from "drizzle-orm"; +import { countUsers, selectUsersWithCompanyAndRoles } from '../admin-users/repository'; +import db from "@/db/db"; +import { getErrorMessage } from "@/lib/handle-error"; + +interface AssignUsersArgs { + roleId: number + userIds: number[] +} + + +export const fetchAllUsers = async (): Promise<User[]> => { + try { + logger.info('Fetching all users'); + const users = await getAllUsers(); + logger.debug({ count: users.length }, 'Fetched users successfully'); + return users; + } catch (error) { + logger.error({ error }, 'Error fetching all users'); + throw new Error('Failed to fetch users'); + } +}; + + +export const fetchRoleAssignedUserID = async (roleId: number) => { + try { + logger.info('Fetching all users'); + const users = await getRoleAssignedUsers(roleId); + logger.debug({ count: users.length }, 'Fetched users successfully'); + return users; + } catch (error) { + logger.error({ error }, 'Error fetching all users'); + throw new Error('Failed to fetch users'); + } +}; + + +export const addNewUser = async (name: string, email: string): Promise<User> => { + try { + logger.info({ name, email }, 'Creating a new user'); + const user = await createUser(name, email); + logger.debug({ user }, 'User created successfully'); + return user; + } catch (error) { + logger.error({ error }, 'Error creating a new user'); + throw new Error('Failed to create user'); + } +}; + +export const findUserById = async (id: number): Promise<User | null> => { + try { + logger.info({ id }, 'Fetching user by ID'); + const user = await getUserById(id); + if (!user) { + logger.warn({ id }, 'User not found'); + } else { + logger.debug({ user }, 'User fetched successfully'); + } + return user; + } catch (error) { + logger.error({ error }, 'Error fetching user by ID'); + throw new Error('Failed to fetch user'); + } +}; + +export const findUserByEmail = async (email: string): Promise<User | null> => { + try { + logger.info({ email }, 'Fetching user by Email'); + const user = await getUserByEmail(email); + if (!user) { + logger.warn({ email }, 'User not found'); + } else { + logger.debug({ user }, 'User fetched successfully'); + } + return user; + } catch (error) { + logger.error({ error }, 'Error fetching user by ID'); + throw new Error('Failed to fetch user'); + } +}; + +export const modifyUser = async (id: number, data: Partial<User>): Promise<User | null> => { + try { + logger.info({ id, data }, 'Updating user'); + const user = await updateUser(id, data); + if (!user) { + logger.warn({ id }, 'User not found for update'); + } else { + logger.debug({ user }, 'User updated successfully'); + } + return user; + } catch (error) { + logger.error({ error }, 'Error updating user'); + throw new Error('Failed to update user'); + } +}; + +export const removeUser = async (id: number): Promise<boolean> => { + try { + logger.info({ id }, 'Deleting user'); + const success = await deleteUser(id); + if (success) { + logger.debug({ id }, 'User deleted successfully'); + } else { + logger.warn({ id }, 'User not found for deletion'); + } + return success; + } catch (error) { + logger.error({ error }, 'Error deleting user'); + throw new Error('Failed to delete user'); + } +}; + +export const addNewOtp = async ( + email: string, + code: string, + createdAt: Date, + otpToken: string, + otpExpires: Date +): Promise<Otp> => { + try { + logger.info({ email }, 'Creating or updating an OTP record'); + + // 1) 먼저 email로 Otp가 있는지 조회 + const existingOtp = await findOtpByEmail(email); + + // 2) 이미 있으면 update + if (existingOtp) { + const otp = await updateOtp(email, code, createdAt, otpToken, otpExpires); + logger.debug({ otp }, 'OTP updated successfully'); + return otp; + } + // 3) 없으면 새로 생성 + else { + const otp = await createOtp(email, code, createdAt, otpToken, otpExpires); + logger.debug({ otp }, 'OTP created successfully'); + return otp; + } + } catch (error) { + logger.error({ error }, 'Error creating or updating OTP'); + throw new Error('Failed to create or update OTP'); + } +}; + +export const findOtpByEmailandToken = async (email: string, otpToken: string): Promise<Otp | null> => { + try { + logger.info({ email }, 'Fetching otp by Email'); + const otp = await getOtpByEmailAndToken(email, otpToken); + if (!otp) { + logger.warn({ email }, 'Otp not found'); + } else { + logger.debug({ otp }, 'Otp fetched successfully'); + } + return otp; + } catch (error) { + logger.error({ error }, 'Error fetching user by ID'); + throw new Error('Failed to fetch user'); + } +}; + + +export async function findEmailandOtp(email: string, code: string) { + try { + // 1) otp 조회 + const otpRecord: Otp | null = await getOtpByEmailAndCode(email, code) + if (!otpRecord) { + return null + } + + // 2) 사용자 정보 추가로 조회 + const userRecord: User | null = await getUserByEmail(email) + if (!userRecord) { + return null + } + + // 3) 필요한 형태로 "통합된 객체"를 반환 + return { + otpExpires: otpRecord.otpExpires, + email: userRecord.email, + name: userRecord.name, // DB 에서 가져온 실제 이름 + id: userRecord.id, // user id + imageUrl:userRecord.imageUrl, + companyId:userRecord.companyId, + domain:userRecord.domain + // 기타 필요한 필드... + } + + } catch (error) { + // 에러 처리 + throw new Error('Failed to fetch user & otp') + } +} + +export async function updateUserProfileImage(formData: FormData) { + // 1) FormData에서 데이터 꺼내기 + const file = formData.get("file") as File | null + const userId = Number(formData.get("userId")) + const name = formData.get("name") as string + const email = formData.get("email") as string + + // 2) 기본적인 유효성 검증 + if (!file) { + throw new Error("No file found in the FormData.") + } + if (!userId) { + throw new Error("userId is required.") + } + + try { + // 3) 파일 저장 (해시 생성) + const directory = './public/profiles' + const { hashedFileName } = await saveDocument(file, directory) + + // 4) DB 업데이트 + const imageUrl = hashedFileName + const data = { name, email, imageUrl } + const user = await updateUser(userId, data) + if (!user) { + // updateUser가 null을 반환하면, DB 업데이트 실패 혹은 해당 유저가 없음 + throw new Error(`User with id=${userId} not found or update failed.`) + } + + // 5) 성공 시 성공 정보 반환 + return { success: true, user } + } catch (err: any) { + // DB 업데이트 중 발생하는 에러나 saveDocument 내부 에러 등을 처리 + console.error("[updateUserProfileImage] Error:", err) + throw new Error(err.message ?? "Failed to update user profile.") + } +} + +export async function getUsersEVCP(input: GetUsersSchema) { + return unstable_cache( + async () => { + try { + const offset = (input.page - 1) * input.perPage; + + // (1) advancedWhere + const advancedWhere = filterColumns({ + table: userView, + filters: input.filters, + joinOperator: input.joinOperator, + }); + + // (2) globalWhere + let globalWhere; + if (input.search) { + const s = `%${input.search}%`; + globalWhere = or( + ilike(userView.user_name, s), + ilike(userView.user_email, s), + ilike(userView.company_name, s) + ); + } + + // (3) 디폴트 domainWhere = eq(userView.domain, "partners") + // 다만, 사용자가 이미 domain 필터를 줬다면 적용 X + let domainWhere; + const hasDomainFilter = input.filters?.some((f) => f.id === "user_domain"); + if (!hasDomainFilter) { + domainWhere = eq(userView.user_domain, "evcp"); + } + + // (4) 최종 where + const finalWhere = and(advancedWhere, globalWhere, domainWhere); + + // (5) 정렬 + const orderBy = + input.sort.length > 0 + ? input.sort.map((item) => + item.desc ? desc(userView[item.id]) : asc(userView[item.id]) + ) + : [desc(users.createdAt)]; + + // ... + const { data, total } = await db.transaction(async (tx) => { + const data = await selectUsersWithCompanyAndRoles(tx, { + where: finalWhere, + orderBy, + offset, + limit: input.perPage, + }); + + const total = await countUsers(tx, finalWhere); + return { data, total }; + }); + + const pageCount = Math.ceil(total / input.perPage); + return { data, pageCount }; + } catch (err) { + return { data: [], pageCount: 0 }; + } + }, + [JSON.stringify(input)], + { + revalidate: 3600, + tags: ["users"], + } + )(); +} + +export async function getAllRoles(): Promise<Role[]> { + try { + return await findAllRoles(); + } catch (err) { + throw new Error("Failed to get roles"); + } +} + + +export async function getUsersAll(input: GetUsersSchema, domain: string) { + return unstable_cache( + async () => { + try { + const offset = (input.page - 1) * input.perPage; + + // (1) advancedWhere + const advancedWhere = filterColumns({ + table: userView, + filters: input.filters, + joinOperator: input.joinOperator, + }); + + // (2) globalWhere + let globalWhere; + if (input.search) { + const s = `%${input.search}%`; + globalWhere = or( + ilike(userView.user_name, s), + ilike(userView.user_email, s), + ilike(userView.company_name, s) + ); + } + + // (3) domainWhere - 무조건 들어가야 하는 domain 조건 + const domainWhere = eq(userView.user_domain, domain); + + // (4) 최종 where + // domainWhere과 advancedWhere, globalWhere를 모두 and로 묶는다. + // (globalWhere가 존재하지 않을 수 있으니, and() 호출 시 undefined를 자동 무시할 수도 있음) + const finalWhere = and(domainWhere, advancedWhere, globalWhere); + + // (5) 정렬 + const orderBy = + input.sort.length > 0 + ? input.sort.map((item) => + item.desc ? desc(userView[item.id]) : asc(userView[item.id]) + ) + : [desc(users.createdAt)]; + + const { data, total } = await db.transaction(async (tx) => { + const data = await selectUsersWithCompanyAndRoles(tx, { + where: finalWhere, + orderBy, + offset, + limit: input.perPage, + }); + + const total = await countUsers(tx, finalWhere); + return { data, total }; + }); + + const pageCount = Math.ceil(total / input.perPage); + return { data, pageCount }; + } catch (err) { + return { data: [], pageCount: 0 }; + } + }, + // (6) 캐시 종속성 배열에 domain도 추가 + [JSON.stringify(input), domain], + { + revalidate: 3600, + tags: ["users"], + } + )(); +} + + +export async function assignUsersToRole(roleId: number, userIds: number[]) { + unstable_noStore(); // 캐싱 방지(Next.js 서버 액션용) + try{ + await db.transaction(async (tx) => { + // 1) 기존 userRoles 레코드 삭제 + await tx.delete(userRoles).where(eq(userRoles.roleId, roleId)) + + // 2) 새로 넣기 + if (userIds.length > 0) { + await tx.insert(userRoles).values( + userIds.map((uid) => ({ userId: uid, roleId })) + ) + } + }) + revalidateTag("users"); + revalidateTag("roles"); + + return { data: null, error: null }; + } catch (err){ + return { data: null, error: getErrorMessage(err) }; + + } + +} |
