summaryrefslogtreecommitdiff
path: root/lib/users/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/users/service.ts')
-rw-r--r--lib/users/service.ts413
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) };
+
+ }
+
+}