@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
559 lines (495 loc) • 16.7 kB
text/typescript
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { PrismaClient } from "@prisma/client";
import { z } from "zod";
import { UserService } from "./service";
import { authConfig } from "@/auth/config";
import { hasPermission } from "@/auth/rbac";
import {
CreateUserRequest,
UpdateUserRequest,
UserListFilters,
UserListSort,
PaginationParams,
PasswordResetRequest,
PasswordResetConfirm,
EmailVerificationRequest,
EmailVerificationConfirm,
AccountUnlockRequest,
ProfileUpdateRequest,
BulkUserOperation,
Role,
} from "./types";
const prisma = new PrismaClient();
const userService = new UserService(prisma);
// 유효성 검사 스키마
const createUserSchema = z.object({
email: z.string().email("유효한 이메일을 입력해주세요"),
password: z.string().min(8, "비밀번호는 최소 8자 이상이어야 합니다"),
name: z.string().min(1, "이름을 입력해주세요"),
role: z.nativeEnum(Role).optional(),
tenantId: z.string().optional(),
sendVerificationEmail: z.boolean().optional(),
});
const updateUserSchema = z.object({
name: z.string().min(1).optional(),
email: z.string().email().optional(),
role: z.nativeEnum(Role).optional(),
isActive: z.boolean().optional(),
emailVerified: z.date().nullable().optional(),
tenantId: z.string().optional(),
});
const passwordResetRequestSchema = z.object({
email: z.string().email("유효한 이메일을 입력해주세요"),
});
const passwordResetConfirmSchema = z.object({
token: z.string().min(1, "토큰이 필요합니다"),
newPassword: z.string().min(8, "비밀번호는 최소 8자 이상이어야 합니다"),
});
const emailVerificationRequestSchema = z.object({
email: z.string().email("유효한 이메일을 입력해주세요"),
});
const emailVerificationConfirmSchema = z.object({
token: z.string().min(1, "토큰이 필요합니다"),
});
const profileUpdateSchema = z.object({
name: z.string().min(1).optional(),
email: z.string().email().optional(),
currentPassword: z.string().optional(),
newPassword: z.string().min(8).optional(),
});
const bulkOperationSchema = z.object({
userIds: z.array(z.string()),
operation: z.enum([
"activate",
"deactivate",
"delete",
"unlock",
"change_role",
]),
data: z
.object({
role: z.nativeEnum(Role).optional(),
})
.optional(),
});
// 사용자 목록 조회
export async function getUsersHandler(request: NextRequest) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_READ")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
const { searchParams } = new URL(request.url);
// 필터 파라미터 파싱
const filters: UserListFilters = {
search: searchParams.get("search") || undefined,
role: (searchParams.get("role") as Role) || undefined,
isActive: searchParams.get("isActive")
? searchParams.get("isActive") === "true"
: undefined,
emailVerified: searchParams.get("emailVerified")
? searchParams.get("emailVerified") === "true"
: undefined,
tenantId: searchParams.get("tenantId") || undefined,
createdAfter: searchParams.get("createdAfter")
? new Date(searchParams.get("createdAfter")!)
: undefined,
createdBefore: searchParams.get("createdBefore")
? new Date(searchParams.get("createdBefore")!)
: undefined,
lastLoginAfter: searchParams.get("lastLoginAfter")
? new Date(searchParams.get("lastLoginAfter")!)
: undefined,
lastLoginBefore: searchParams.get("lastLoginBefore")
? new Date(searchParams.get("lastLoginBefore")!)
: undefined,
};
// 정렬 파라미터 파싱
const sort: UserListSort = {
field:
(searchParams.get("sortField") as keyof UserListSort["field"]) ||
"createdAt",
direction:
(searchParams.get("sortDirection") as "asc" | "desc") || "desc",
};
// 페이지네이션 파라미터 파싱
const pagination: PaginationParams = {
page: parseInt(searchParams.get("page") || "1"),
limit: parseInt(searchParams.get("limit") || "20"),
};
const result = await userService.getUsers(filters, sort, pagination);
return NextResponse.json(result);
} catch (error) {
console.error("사용자 목록 조회 오류:", error);
return NextResponse.json(
{ error: "사용자 목록을 조회하는 중 오류가 발생했습니다" },
{ status: 500 }
);
}
}
// 사용자 생성
export async function createUserHandler(request: NextRequest) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_CREATE")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
const body = await request.json();
const validatedData = createUserSchema.parse(body);
const user = await userService.createUser(
validatedData as CreateUserRequest,
session.user.id
);
return NextResponse.json(user, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("사용자 생성 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "사용자 생성 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 사용자 조회
export async function getUserHandler(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_READ")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
const { searchParams } = new URL(request.url);
const includeTenant = searchParams.get("includeTenant") === "true";
const includeStats = searchParams.get("includeStats") === "true";
const user = await userService.getUserById(params.id, {
includeTenant,
includeStats,
});
if (!user) {
return NextResponse.json(
{ error: "사용자를 찾을 수 없습니다" },
{ status: 404 }
);
}
return NextResponse.json(user);
} catch (error) {
console.error("사용자 조회 오류:", error);
return NextResponse.json(
{ error: "사용자 조회 중 오류가 발생했습니다" },
{ status: 500 }
);
}
}
// 사용자 업데이트
export async function updateUserHandler(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_UPDATE")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
const body = await request.json();
const validatedData = updateUserSchema.parse(body);
const user = await userService.updateUser(
params.id,
validatedData as UpdateUserRequest,
session.user.id
);
return NextResponse.json(user);
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("사용자 업데이트 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "사용자 업데이트 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 사용자 삭제
export async function deleteUserHandler(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_DELETE")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
await userService.deleteUser(params.id, session.user.id);
return NextResponse.json({ message: "사용자가 삭제되었습니다" });
} catch (error) {
console.error("사용자 삭제 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "사용자 삭제 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 비밀번호 재설정 요청
export async function requestPasswordResetHandler(request: NextRequest) {
try {
const body = await request.json();
const validatedData = passwordResetRequestSchema.parse(body);
await userService.requestPasswordReset(
validatedData as PasswordResetRequest
);
return NextResponse.json({
message: "비밀번호 재설정 이메일이 발송되었습니다",
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("비밀번호 재설정 요청 오류:", error);
return NextResponse.json(
{ error: "비밀번호 재설정 요청 중 오류가 발생했습니다" },
{ status: 500 }
);
}
}
// 비밀번호 재설정 확인
export async function confirmPasswordResetHandler(request: NextRequest) {
try {
const body = await request.json();
const validatedData = passwordResetConfirmSchema.parse(body);
await userService.confirmPasswordReset(
validatedData as PasswordResetConfirm
);
return NextResponse.json({ message: "비밀번호가 재설정되었습니다" });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("비밀번호 재설정 확인 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "비밀번호 재설정 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 이메일 인증 요청
export async function requestEmailVerificationHandler(request: NextRequest) {
try {
const body = await request.json();
const validatedData = emailVerificationRequestSchema.parse(body);
await userService.requestEmailVerification(
validatedData as EmailVerificationRequest
);
return NextResponse.json({ message: "이메일 인증 메일이 발송되었습니다" });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("이메일 인증 요청 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "이메일 인증 요청 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 이메일 인증 확인
export async function confirmEmailVerificationHandler(request: NextRequest) {
try {
const body = await request.json();
const validatedData = emailVerificationConfirmSchema.parse(body);
await userService.confirmEmailVerification(
validatedData as EmailVerificationConfirm
);
return NextResponse.json({ message: "이메일이 인증되었습니다" });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("이메일 인증 확인 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "이메일 인증 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 계정 잠금 해제
export async function unlockAccountHandler(request: NextRequest) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_UPDATE")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
const body = await request.json();
const { userId, reason } = body;
await userService.unlockAccount(
{ userId, reason } as AccountUnlockRequest,
session.user.id
);
return NextResponse.json({ message: "계정 잠금이 해제되었습니다" });
} catch (error) {
console.error("계정 잠금 해제 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "계정 잠금 해제 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 프로필 업데이트
export async function updateProfileHandler(request: NextRequest) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
const body = await request.json();
const validatedData = profileUpdateSchema.parse(body);
const user = await userService.updateProfile(
session.user.id,
validatedData as ProfileUpdateRequest
);
return NextResponse.json(user);
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("프로필 업데이트 오류:", error);
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "프로필 업데이트 중 오류가 발생했습니다",
},
{ status: 500 }
);
}
}
// 사용자 통계 조회
export async function getUserStatsHandler(request: NextRequest) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_READ")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
const stats = await userService.getUserStats();
return NextResponse.json(stats);
} catch (error) {
console.error("사용자 통계 조회 오류:", error);
return NextResponse.json(
{ error: "사용자 통계 조회 중 오류가 발생했습니다" },
{ status: 500 }
);
}
}
// 대량 사용자 작업
export async function bulkUserOperationHandler(request: NextRequest) {
try {
const session = await getServerSession(authConfig);
if (!session?.user) {
return NextResponse.json({ error: "인증이 필요합니다" }, { status: 401 });
}
if (!hasPermission(session.user, "USER_UPDATE")) {
return NextResponse.json({ error: "권한이 없습니다" }, { status: 403 });
}
const body = await request.json();
const validatedData = bulkOperationSchema.parse(body);
const result = await userService.bulkUserOperation(
validatedData as BulkUserOperation,
session.user.id
);
return NextResponse.json(result);
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "유효하지 않은 데이터", details: error.errors },
{ status: 400 }
);
}
console.error("대량 사용자 작업 오류:", error);
return NextResponse.json(
{ error: "대량 사용자 작업 중 오류가 발생했습니다" },
{ status: 500 }
);
}
}