diff --git a/API_USAGE.md b/API_USAGE.md
deleted file mode 100644
index 77a73a6..0000000
--- a/API_USAGE.md
+++ /dev/null
@@ -1,565 +0,0 @@
-# API 사용 가이드
-
-이 문서는 데이터베이스에 데이터를 생성하는 API의 사용 방법을 설명합니다.
-
-## 📋 목차
-
-1. [기본 설정](#기본-설정)
-2. [사용자 API](#1-사용자-api)
-3. [교육과정 API](#2-교육과정-api)
-4. [강좌 API](#3-강좌-api)
-5. [공지사항 API](#4-공지사항-api)
-6. [에러 처리](#에러-처리)
-7. [실전 예제](#실전-예제)
-
----
-
-## 기본 설정
-
-### 환경 변수
-
-`.env` 파일에 데이터베이스 연결 정보가 설정되어 있어야 합니다:
-
-```env
-DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
-```
-
-### API 기본 URL
-
-- 개발 환경: `http://localhost:3000/api`
-- 프로덕션: `https://your-domain.com/api`
-
----
-
-## 1. 사용자 API
-
-### POST /api/users - 사용자 생성
-
-새로운 사용자를 생성합니다.
-
-#### 요청
-
-```typescript
-const response = await fetch('/api/users', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- email: 'user@example.com',
- password: 'hashed_password_here', // 실제로는 해시화된 비밀번호
- name: '홍길동',
- phone: '010-1234-5678', // 선택사항
- gender: 'M', // 선택사항: 'M' 또는 'F'
- birthdate: '1990-01-01', // 선택사항: YYYY-MM-DD 형식
- role: 'LEARNER', // 선택사항: 'LEARNER' 또는 'ADMIN' (기본값: 'LEARNER')
- status: 'ACTIVE', // 선택사항: 'ACTIVE' 또는 'INACTIVE' (기본값: 'ACTIVE')
- }),
-});
-
-const data = await response.json();
-```
-
-#### 성공 응답 (201)
-
-```json
-{
- "message": "사용자가 성공적으로 생성되었습니다.",
- "user": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "email": "user@example.com",
- "name": "홍길동",
- "phone": "010-1234-5678",
- "gender": "M",
- "birthdate": "1990-01-01T00:00:00.000Z",
- "role": "LEARNER",
- "status": "ACTIVE",
- "joinDate": "2024-11-21T00:00:00.000Z",
- "createdAt": "2024-11-21T00:00:00.000Z",
- "updatedAt": "2024-11-21T00:00:00.000Z"
- }
-}
-```
-
-#### 에러 응답
-
-**400 Bad Request** - 필수 필드 누락
-```json
-{
- "error": "이메일, 비밀번호, 이름은 필수입니다."
-}
-```
-
-**409 Conflict** - 이메일 중복
-```json
-{
- "error": "이미 존재하는 이메일입니다."
-}
-```
-
-### GET /api/users - 사용자 목록 조회
-
-사용자 목록을 조회합니다. 필터링 및 페이지네이션을 지원합니다.
-
-#### 요청
-
-```typescript
-// 전체 사용자 조회
-const response = await fetch('/api/users');
-
-// 필터링 및 페이지네이션
-const response = await fetch('/api/users?role=LEARNER&status=ACTIVE&page=1&limit=10');
-
-const data = await response.json();
-```
-
-#### 쿼리 파라미터
-
-- `role` (선택): `LEARNER` 또는 `ADMIN`
-- `status` (선택): `ACTIVE` 또는 `INACTIVE`
-- `page` (선택): 페이지 번호 (기본값: 1)
-- `limit` (선택): 페이지당 항목 수 (기본값: 10)
-
-#### 성공 응답 (200)
-
-```json
-{
- "users": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "email": "user@example.com",
- "name": "홍길동",
- "role": "LEARNER",
- "status": "ACTIVE",
- ...
- }
- ],
- "pagination": {
- "page": 1,
- "limit": 10,
- "total": 30,
- "totalPages": 3
- }
-}
-```
-
----
-
-## 2. 교육과정 API
-
-### POST /api/courses - 교육과정 생성
-
-새로운 교육과정을 생성합니다.
-
-#### 요청
-
-```typescript
-const response = await fetch('/api/courses', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- courseName: '웹 개발 기초',
- instructorId: 'instructor-uuid-here', // 필수: 강사(ADMIN 역할)의 ID
- createdById: 'admin-uuid-here', // 선택사항: 등록자 ID (기본값: instructorId)
- }),
-});
-
-const data = await response.json();
-```
-
-#### 성공 응답 (201)
-
-```json
-{
- "message": "교육과정이 성공적으로 생성되었습니다.",
- "course": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "courseName": "웹 개발 기초",
- "instructorId": "instructor-uuid",
- "createdById": "admin-uuid",
- "createdAt": "2024-11-21T00:00:00.000Z",
- "instructor": {
- "id": "instructor-uuid",
- "name": "최예준",
- "email": "instructor@example.com"
- },
- "createdBy": {
- "id": "admin-uuid",
- "name": "관리자"
- }
- }
-}
-```
-
-#### 에러 응답
-
-**400 Bad Request** - 필수 필드 누락
-```json
-{
- "error": "교육과정명과 강사 ID는 필수입니다."
-}
-```
-
-**404 Not Found** - 강사를 찾을 수 없음
-```json
-{
- "error": "강사를 찾을 수 없습니다."
-}
-```
-
-### GET /api/courses - 교육과정 목록 조회
-
-교육과정 목록을 조회합니다.
-
-#### 요청
-
-```typescript
-// 전체 교육과정 조회
-const response = await fetch('/api/courses');
-
-// 특정 강사의 교육과정 조회
-const response = await fetch('/api/courses?instructorId=instructor-uuid&page=1&limit=10');
-
-const data = await response.json();
-```
-
-#### 쿼리 파라미터
-
-- `instructorId` (선택): 강사 ID로 필터링
-- `page` (선택): 페이지 번호 (기본값: 1)
-- `limit` (선택): 페이지당 항목 수 (기본값: 10)
-
----
-
-## 3. 강좌 API
-
-### POST /api/lessons - 강좌 생성
-
-새로운 강좌를 생성합니다.
-
-#### 요청
-
-```typescript
-const response = await fetch('/api/lessons', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- courseId: 'course-uuid-here', // 필수: 교육과정 ID
- lessonName: 'HTML 기초', // 필수: 강좌명
- learningGoal: 'HTML의 기본 문법을 이해하고 활용할 수 있다.', // 선택사항: 학습 목표
- createdById: 'admin-uuid-here', // 선택사항: 등록자 ID
- }),
-});
-
-const data = await response.json();
-```
-
-#### 성공 응답 (201)
-
-```json
-{
- "message": "강좌가 성공적으로 생성되었습니다.",
- "lesson": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "courseId": "course-uuid",
- "lessonName": "HTML 기초",
- "learningGoal": "HTML의 기본 문법을 이해하고 활용할 수 있다.",
- "createdAt": "2024-11-21T00:00:00.000Z",
- "course": {
- "id": "course-uuid",
- "courseName": "웹 개발 기초"
- },
- "createdBy": {
- "id": "admin-uuid",
- "name": "관리자"
- },
- "_count": {
- "videos": 0,
- "vrContents": 0,
- "questions": 0
- }
- }
-}
-```
-
-### GET /api/lessons - 강좌 목록 조회
-
-강좌 목록을 조회합니다.
-
-#### 요청
-
-```typescript
-// 전체 강좌 조회
-const response = await fetch('/api/lessons');
-
-// 특정 교육과정의 강좌 조회
-const response = await fetch('/api/lessons?courseId=course-uuid&page=1&limit=10');
-
-const data = await response.json();
-```
-
----
-
-## 4. 공지사항 API
-
-### POST /api/notices - 공지사항 생성
-
-새로운 공지사항을 생성합니다.
-
-#### 요청
-
-```typescript
-const response = await fetch('/api/notices', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- title: '공지사항 제목',
- content: '공지사항 내용입니다.\n여러 줄로 작성할 수 있습니다.',
- writerId: 'admin-uuid-here', // 필수: 작성자 ID
- hasAttachment: false, // 선택사항: 첨부파일 여부 (기본값: false)
- }),
-});
-
-const data = await response.json();
-```
-
-#### 성공 응답 (201)
-
-```json
-{
- "message": "공지사항이 성공적으로 생성되었습니다.",
- "notice": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "title": "공지사항 제목",
- "content": "공지사항 내용입니다.\n여러 줄로 작성할 수 있습니다.",
- "writerId": "admin-uuid",
- "views": 0,
- "hasAttachment": false,
- "date": "2024-11-21T00:00:00.000Z",
- "writer": {
- "id": "admin-uuid",
- "name": "관리자",
- "email": "admin@example.com"
- }
- }
-}
-```
-
-### GET /api/notices - 공지사항 목록 조회
-
-공지사항 목록을 조회합니다.
-
-#### 요청
-
-```typescript
-// 전체 공지사항 조회
-const response = await fetch('/api/notices');
-
-// 특정 작성자의 공지사항 조회
-const response = await fetch('/api/notices?writerId=admin-uuid&page=1&limit=10');
-
-const data = await response.json();
-```
-
----
-
-## 에러 처리
-
-모든 API는 일관된 에러 응답 형식을 사용합니다:
-
-```typescript
-try {
- const response = await fetch('/api/users', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(userData),
- });
-
- if (!response.ok) {
- const error = await response.json();
- console.error('에러:', error.error);
- // 에러 처리 로직
- return;
- }
-
- const data = await response.json();
- console.log('성공:', data);
-} catch (error) {
- console.error('네트워크 오류:', error);
-}
-```
-
-### HTTP 상태 코드
-
-- `200` - 성공 (GET 요청)
-- `201` - 생성 성공 (POST 요청)
-- `400` - 잘못된 요청 (필수 필드 누락 등)
-- `404` - 리소스를 찾을 수 없음
-- `409` - 충돌 (중복 데이터 등)
-- `500` - 서버 오류
-
----
-
-## 실전 예제
-
-### React 컴포넌트에서 사용하기
-
-```typescript
-'use client';
-
-import { useState } from 'react';
-
-export default function CreateUserForm() {
- const [formData, setFormData] = useState({
- email: '',
- password: '',
- name: '',
- role: 'LEARNER',
- });
- const [loading, setLoading] = useState(false);
- const [message, setMessage] = useState('');
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setLoading(true);
- setMessage('');
-
- try {
- const response = await fetch('/api/users', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(formData),
- });
-
- const data = await response.json();
-
- if (!response.ok) {
- setMessage(`오류: ${data.error}`);
- return;
- }
-
- setMessage('사용자가 성공적으로 생성되었습니다!');
- // 폼 초기화
- setFormData({ email: '', password: '', name: '', role: 'LEARNER' });
- } catch (error) {
- setMessage('네트워크 오류가 발생했습니다.');
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
- );
-}
-```
-
-### Server Component에서 사용하기
-
-```typescript
-// app/admin/users/page.tsx
-import { prisma } from '@/lib/prisma';
-
-export default async function UsersPage() {
- const users = await prisma.user.findMany({
- take: 10,
- orderBy: { createdAt: 'desc' },
- });
-
- return (
-
-
사용자 목록
-
- {users.map((user) => (
- -
- {user.name} ({user.email}) - {user.role}
-
- ))}
-
-
- );
-}
-```
-
-### cURL로 테스트하기
-
-```bash
-# 사용자 생성
-curl -X POST http://localhost:3000/api/users \
- -H "Content-Type: application/json" \
- -d '{
- "email": "test@example.com",
- "password": "test123",
- "name": "테스트 사용자",
- "role": "LEARNER"
- }'
-
-# 사용자 목록 조회
-curl http://localhost:3000/api/users?role=LEARNER&page=1&limit=10
-
-# 교육과정 생성
-curl -X POST http://localhost:3000/api/courses \
- -H "Content-Type: application/json" \
- -d '{
- "courseName": "웹 개발 기초",
- "instructorId": "instructor-uuid-here"
- }'
-```
-
----
-
-## 🔒 보안 고려사항
-
-1. **비밀번호 해시화**: 실제 프로덕션에서는 bcrypt 등을 사용하여 비밀번호를 해시화해야 합니다.
-2. **인증/인가**: 현재 API는 인증이 없습니다. 프로덕션에서는 JWT 또는 세션 기반 인증을 추가해야 합니다.
-3. **입력 검증**: 클라이언트 측 검증 외에도 서버 측 검증이 필요합니다.
-4. **CORS 설정**: 필요시 CORS 설정을 추가해야 합니다.
-
----
-
-## 📚 추가 리소스
-
-- [Next.js API Routes 문서](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
-- [Prisma Client 문서](https://www.prisma.io/docs/concepts/components/prisma-client)
-
diff --git a/prisma/migrations/20251121074818_new/migration.sql b/prisma/migrations/20251121074818_new/migration.sql
deleted file mode 100644
index e0ede73..0000000
--- a/prisma/migrations/20251121074818_new/migration.sql
+++ /dev/null
@@ -1,311 +0,0 @@
--- CreateEnum
-CREATE TYPE "UserRole" AS ENUM ('LEARNER', 'ADMIN');
-
--- CreateEnum
-CREATE TYPE "UserStatus" AS ENUM ('ACTIVE', 'INACTIVE');
-
--- CreateEnum
-CREATE TYPE "QuestionType" AS ENUM ('MULTIPLE_CHOICE', 'SHORT_ANSWER', 'ESSAY');
-
--- CreateTable
-CREATE TABLE "users" (
- "id" TEXT NOT NULL,
- "email" TEXT NOT NULL,
- "password" TEXT NOT NULL,
- "name" TEXT NOT NULL,
- "phone" TEXT,
- "gender" TEXT,
- "birthdate" TIMESTAMP(3),
- "role" "UserRole" NOT NULL DEFAULT 'LEARNER',
- "status" "UserStatus" NOT NULL DEFAULT 'ACTIVE',
- "joinDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updatedAt" TIMESTAMP(3) NOT NULL,
-
- CONSTRAINT "users_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "courses" (
- "id" TEXT NOT NULL,
- "courseName" TEXT NOT NULL,
- "instructorId" TEXT NOT NULL,
- "createdById" TEXT NOT NULL,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updatedAt" TIMESTAMP(3) NOT NULL,
-
- CONSTRAINT "courses_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "lessons" (
- "id" TEXT NOT NULL,
- "courseId" TEXT NOT NULL,
- "lessonName" TEXT NOT NULL,
- "learningGoal" TEXT,
- "createdById" TEXT NOT NULL,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updatedAt" TIMESTAMP(3) NOT NULL,
-
- CONSTRAINT "lessons_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "lesson_videos" (
- "id" TEXT NOT NULL,
- "lessonId" TEXT NOT NULL,
- "fileName" TEXT NOT NULL,
- "filePath" TEXT NOT NULL,
- "fileSize" INTEGER NOT NULL,
- "order" INTEGER NOT NULL,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-
- CONSTRAINT "lesson_videos_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "lesson_vr_contents" (
- "id" TEXT NOT NULL,
- "lessonId" TEXT NOT NULL,
- "fileName" TEXT NOT NULL,
- "filePath" TEXT NOT NULL,
- "fileSize" INTEGER NOT NULL,
- "order" INTEGER NOT NULL,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-
- CONSTRAINT "lesson_vr_contents_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "lesson_attachments" (
- "id" TEXT NOT NULL,
- "lessonId" TEXT NOT NULL,
- "fileName" TEXT NOT NULL,
- "filePath" TEXT NOT NULL,
- "fileSize" INTEGER NOT NULL,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-
- CONSTRAINT "lesson_attachments_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "questions" (
- "id" TEXT NOT NULL,
- "lessonId" TEXT,
- "question" TEXT NOT NULL,
- "type" "QuestionType" NOT NULL DEFAULT 'MULTIPLE_CHOICE',
- "options" JSONB,
- "correctAnswer" TEXT NOT NULL,
- "explanation" TEXT,
- "points" INTEGER NOT NULL DEFAULT 1,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updatedAt" TIMESTAMP(3) NOT NULL,
-
- CONSTRAINT "questions_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "notices" (
- "id" TEXT NOT NULL,
- "title" TEXT NOT NULL,
- "content" TEXT NOT NULL,
- "writerId" TEXT NOT NULL,
- "views" INTEGER NOT NULL DEFAULT 0,
- "hasAttachment" BOOLEAN NOT NULL DEFAULT false,
- "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updatedAt" TIMESTAMP(3) NOT NULL,
-
- CONSTRAINT "notices_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "notice_attachments" (
- "id" TEXT NOT NULL,
- "noticeId" TEXT NOT NULL,
- "fileName" TEXT NOT NULL,
- "filePath" TEXT NOT NULL,
- "fileSize" INTEGER NOT NULL,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-
- CONSTRAINT "notice_attachments_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "resources" (
- "id" TEXT NOT NULL,
- "title" TEXT NOT NULL,
- "description" TEXT,
- "filePath" TEXT,
- "fileName" TEXT,
- "fileSize" INTEGER,
- "category" TEXT,
- "views" INTEGER NOT NULL DEFAULT 0,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updatedAt" TIMESTAMP(3) NOT NULL,
-
- CONSTRAINT "resources_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "enrollments" (
- "id" TEXT NOT NULL,
- "userId" TEXT NOT NULL,
- "lessonId" TEXT NOT NULL,
- "progress" INTEGER NOT NULL DEFAULT 0,
- "completedAt" TIMESTAMP(3),
- "enrolledAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-
- CONSTRAINT "enrollments_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "certificates" (
- "id" TEXT NOT NULL,
- "userId" TEXT NOT NULL,
- "lessonId" TEXT,
- "courseId" TEXT,
- "verificationKey" TEXT NOT NULL,
- "issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-
- CONSTRAINT "certificates_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "logs" (
- "id" TEXT NOT NULL,
- "userId" TEXT,
- "action" TEXT NOT NULL,
- "ipAddress" TEXT,
- "userAgent" TEXT,
- "details" JSONB,
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-
- CONSTRAINT "logs_pkey" PRIMARY KEY ("id")
-);
-
--- CreateIndex
-CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
-
--- CreateIndex
-CREATE INDEX "users_email_idx" ON "users"("email");
-
--- CreateIndex
-CREATE INDEX "users_role_idx" ON "users"("role");
-
--- CreateIndex
-CREATE INDEX "users_status_idx" ON "users"("status");
-
--- CreateIndex
-CREATE INDEX "courses_instructorId_idx" ON "courses"("instructorId");
-
--- CreateIndex
-CREATE INDEX "courses_createdById_idx" ON "courses"("createdById");
-
--- CreateIndex
-CREATE INDEX "courses_createdAt_idx" ON "courses"("createdAt");
-
--- CreateIndex
-CREATE INDEX "lessons_courseId_idx" ON "lessons"("courseId");
-
--- CreateIndex
-CREATE INDEX "lessons_createdById_idx" ON "lessons"("createdById");
-
--- CreateIndex
-CREATE INDEX "lessons_createdAt_idx" ON "lessons"("createdAt");
-
--- CreateIndex
-CREATE INDEX "lesson_videos_lessonId_idx" ON "lesson_videos"("lessonId");
-
--- CreateIndex
-CREATE INDEX "lesson_vr_contents_lessonId_idx" ON "lesson_vr_contents"("lessonId");
-
--- CreateIndex
-CREATE INDEX "lesson_attachments_lessonId_idx" ON "lesson_attachments"("lessonId");
-
--- CreateIndex
-CREATE INDEX "questions_lessonId_idx" ON "questions"("lessonId");
-
--- CreateIndex
-CREATE INDEX "notices_writerId_idx" ON "notices"("writerId");
-
--- CreateIndex
-CREATE INDEX "notices_date_idx" ON "notices"("date");
-
--- CreateIndex
-CREATE UNIQUE INDEX "notice_attachments_noticeId_key" ON "notice_attachments"("noticeId");
-
--- CreateIndex
-CREATE INDEX "resources_category_idx" ON "resources"("category");
-
--- CreateIndex
-CREATE INDEX "resources_createdAt_idx" ON "resources"("createdAt");
-
--- CreateIndex
-CREATE INDEX "enrollments_userId_idx" ON "enrollments"("userId");
-
--- CreateIndex
-CREATE INDEX "enrollments_lessonId_idx" ON "enrollments"("lessonId");
-
--- CreateIndex
-CREATE UNIQUE INDEX "enrollments_userId_lessonId_key" ON "enrollments"("userId", "lessonId");
-
--- CreateIndex
-CREATE UNIQUE INDEX "certificates_verificationKey_key" ON "certificates"("verificationKey");
-
--- CreateIndex
-CREATE INDEX "certificates_userId_idx" ON "certificates"("userId");
-
--- CreateIndex
-CREATE INDEX "certificates_verificationKey_idx" ON "certificates"("verificationKey");
-
--- CreateIndex
-CREATE INDEX "logs_userId_idx" ON "logs"("userId");
-
--- CreateIndex
-CREATE INDEX "logs_action_idx" ON "logs"("action");
-
--- CreateIndex
-CREATE INDEX "logs_createdAt_idx" ON "logs"("createdAt");
-
--- AddForeignKey
-ALTER TABLE "courses" ADD CONSTRAINT "courses_instructorId_fkey" FOREIGN KEY ("instructorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "courses" ADD CONSTRAINT "courses_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "lessons" ADD CONSTRAINT "lessons_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "courses"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "lessons" ADD CONSTRAINT "lessons_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "lesson_videos" ADD CONSTRAINT "lesson_videos_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "lessons"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "lesson_vr_contents" ADD CONSTRAINT "lesson_vr_contents_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "lessons"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "lesson_attachments" ADD CONSTRAINT "lesson_attachments_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "lessons"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "questions" ADD CONSTRAINT "questions_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "lessons"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "notices" ADD CONSTRAINT "notices_writerId_fkey" FOREIGN KEY ("writerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "notice_attachments" ADD CONSTRAINT "notice_attachments_noticeId_fkey" FOREIGN KEY ("noticeId") REFERENCES "notices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "enrollments" ADD CONSTRAINT "enrollments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "enrollments" ADD CONSTRAINT "enrollments_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "lessons"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "certificates" ADD CONSTRAINT "certificates_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "logs" ADD CONSTRAINT "logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
deleted file mode 100644
index 044d57c..0000000
--- a/prisma/migrations/migration_lock.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-# Please do not edit this file manually
-# It should be added in your version-control system (e.g., Git)
-provider = "postgresql"
diff --git a/src/app/api/README.md b/src/app/api/README.md
deleted file mode 100644
index 9fba067..0000000
--- a/src/app/api/README.md
+++ /dev/null
@@ -1,211 +0,0 @@
-# API 엔드포인트 문서
-
-이 문서는 데이터베이스에 데이터를 생성하는 API 엔드포인트를 설명합니다.
-
-## 📋 API 목록
-
-### 1. 사용자 API (`/api/users`)
-
-#### POST - 사용자 생성
-```bash
-POST /api/users
-Content-Type: application/json
-
-{
- "email": "user@example.com",
- "password": "hashed_password",
- "name": "홍길동",
- "phone": "010-1234-5678",
- "gender": "M",
- "birthdate": "1990-01-01",
- "role": "LEARNER", // 또는 "ADMIN"
- "status": "ACTIVE" // 또는 "INACTIVE"
-}
-```
-
-**응답:**
-```json
-{
- "message": "사용자가 성공적으로 생성되었습니다.",
- "user": {
- "id": "uuid",
- "email": "user@example.com",
- "name": "홍길동",
- ...
- }
-}
-```
-
-#### GET - 사용자 목록 조회
-```bash
-GET /api/users?role=LEARNER&status=ACTIVE&page=1&limit=10
-```
-
-**쿼리 파라미터:**
-- `role`: 필터링할 역할 (LEARNER, ADMIN)
-- `status`: 필터링할 상태 (ACTIVE, INACTIVE)
-- `page`: 페이지 번호 (기본값: 1)
-- `limit`: 페이지당 항목 수 (기본값: 10)
-
----
-
-### 2. 교육과정 API (`/api/courses`)
-
-#### POST - 교육과정 생성
-```bash
-POST /api/courses
-Content-Type: application/json
-
-{
- "courseName": "웹 개발 기초",
- "instructorId": "instructor_uuid",
- "createdById": "admin_uuid" // 선택사항, 기본값: instructorId
-}
-```
-
-**응답:**
-```json
-{
- "message": "교육과정이 성공적으로 생성되었습니다.",
- "course": {
- "id": "uuid",
- "courseName": "웹 개발 기초",
- "instructor": { ... },
- "createdBy": { ... }
- }
-}
-```
-
-#### GET - 교육과정 목록 조회
-```bash
-GET /api/courses?instructorId=uuid&page=1&limit=10
-```
-
----
-
-### 3. 강좌 API (`/api/lessons`)
-
-#### POST - 강좌 생성
-```bash
-POST /api/lessons
-Content-Type: application/json
-
-{
- "courseId": "course_uuid",
- "lessonName": "HTML 기초",
- "learningGoal": "HTML의 기본 문법을 이해하고 활용할 수 있다.",
- "createdById": "admin_uuid" // 선택사항
-}
-```
-
-**응답:**
-```json
-{
- "message": "강좌가 성공적으로 생성되었습니다.",
- "lesson": {
- "id": "uuid",
- "lessonName": "HTML 기초",
- "course": { ... },
- "createdBy": { ... }
- }
-}
-```
-
-#### GET - 강좌 목록 조회
-```bash
-GET /api/lessons?courseId=uuid&page=1&limit=10
-```
-
----
-
-### 4. 공지사항 API (`/api/notices`)
-
-#### POST - 공지사항 생성
-```bash
-POST /api/notices
-Content-Type: application/json
-
-{
- "title": "공지사항 제목",
- "content": "공지사항 내용",
- "writerId": "admin_uuid",
- "hasAttachment": false // 선택사항
-}
-```
-
-**응답:**
-```json
-{
- "message": "공지사항이 성공적으로 생성되었습니다.",
- "notice": {
- "id": "uuid",
- "title": "공지사항 제목",
- "content": "공지사항 내용",
- "writer": { ... }
- }
-}
-```
-
-#### GET - 공지사항 목록 조회
-```bash
-GET /api/notices?writerId=uuid&page=1&limit=10
-```
-
----
-
-## 🔧 사용 예시
-
-### JavaScript/TypeScript (fetch)
-
-```typescript
-// 사용자 생성
-const response = await fetch('/api/users', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- email: 'user@example.com',
- password: 'hashed_password',
- name: '홍길동',
- role: 'LEARNER',
- }),
-});
-
-const data = await response.json();
-console.log(data);
-```
-
-### cURL
-
-```bash
-# 사용자 생성
-curl -X POST http://localhost:3000/api/users \
- -H "Content-Type: application/json" \
- -d '{
- "email": "user@example.com",
- "password": "hashed_password",
- "name": "홍길동",
- "role": "LEARNER"
- }'
-```
-
----
-
-## ⚠️ 주의사항
-
-1. **비밀번호 해시화**: 실제 프로덕션에서는 비밀번호를 해시화하여 저장해야 합니다.
-2. **인증/인가**: 현재 API는 인증이 없습니다. 프로덕션에서는 JWT나 세션 기반 인증을 추가해야 합니다.
-3. **에러 처리**: 모든 API는 적절한 에러 응답을 반환합니다.
-4. **데이터 검증**: 필수 필드 검증이 포함되어 있습니다.
-
----
-
-## 📝 다음 단계
-
-- [ ] 인증 미들웨어 추가
-- [ ] 비밀번호 해시화 로직 추가
-- [ ] 파일 업로드 API 추가 (공지사항 첨부파일 등)
-- [ ] 수정/삭제 API 추가
-- [ ] 상세 조회 API 추가
-
diff --git a/src/app/api/courses/route.ts b/src/app/api/courses/route.ts
deleted file mode 100644
index b2407ce..0000000
--- a/src/app/api/courses/route.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { prisma } from '@/lib/prisma';
-
-// 교육과정 생성
-export async function POST(request: NextRequest) {
- try {
- const body = await request.json();
- const { courseName, instructorId, createdById } = body;
-
- // 필수 필드 검증
- if (!courseName || !instructorId) {
- return NextResponse.json(
- { error: '교육과정명과 강사 ID는 필수입니다.' },
- { status: 400 }
- );
- }
-
- // 강사 존재 확인
- const instructor = await prisma.user.findUnique({
- where: { id: instructorId },
- });
-
- if (!instructor) {
- return NextResponse.json(
- { error: '강사를 찾을 수 없습니다.' },
- { status: 404 }
- );
- }
-
- // 교육과정 생성
- const course = await prisma.course.create({
- data: {
- courseName,
- instructorId,
- createdById: createdById || instructorId,
- },
- include: {
- instructor: {
- select: {
- id: true,
- name: true,
- email: true,
- },
- },
- createdBy: {
- select: {
- id: true,
- name: true,
- },
- },
- },
- });
-
- return NextResponse.json(
- { message: '교육과정이 성공적으로 생성되었습니다.', course },
- { status: 201 }
- );
- } catch (error) {
- console.error('교육과정 생성 오류:', error);
- return NextResponse.json(
- { error: '교육과정 생성 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
-// 교육과정 목록 조회
-export async function GET(request: NextRequest) {
- try {
- const { searchParams } = new URL(request.url);
- const instructorId = searchParams.get('instructorId');
- const page = parseInt(searchParams.get('page') || '1');
- const limit = parseInt(searchParams.get('limit') || '10');
- const skip = (page - 1) * limit;
-
- const where: any = {};
- if (instructorId) where.instructorId = instructorId;
-
- const [courses, total] = await Promise.all([
- prisma.course.findMany({
- where,
- skip,
- take: limit,
- orderBy: { createdAt: 'desc' },
- include: {
- instructor: {
- select: {
- id: true,
- name: true,
- email: true,
- },
- },
- createdBy: {
- select: {
- id: true,
- name: true,
- },
- },
- _count: {
- select: {
- lessons: true,
- },
- },
- },
- }),
- prisma.course.count({ where }),
- ]);
-
- return NextResponse.json({
- courses,
- pagination: {
- page,
- limit,
- total,
- totalPages: Math.ceil(total / limit),
- },
- });
- } catch (error) {
- console.error('교육과정 조회 오류:', error);
- return NextResponse.json(
- { error: '교육과정 조회 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
diff --git a/src/app/api/examples.ts b/src/app/api/examples.ts
deleted file mode 100644
index 0ecbfe7..0000000
--- a/src/app/api/examples.ts
+++ /dev/null
@@ -1,272 +0,0 @@
-/**
- * API 사용 예제 모음
- *
- * 이 파일은 API를 사용하는 다양한 예제를 제공합니다.
- * 실제 프로젝트에서 참고하여 사용하세요.
- */
-
-// ============================================
-// 1. 사용자 API 예제
-// ============================================
-
-/**
- * 사용자 생성 예제
- */
-export async function createUserExample() {
- const response = await fetch('/api/users', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- email: 'newuser@example.com',
- password: 'hashed_password_here', // 실제로는 bcrypt로 해시화
- name: '홍길동',
- phone: '010-1234-5678',
- gender: 'M',
- birthdate: '1990-01-01',
- role: 'LEARNER',
- status: 'ACTIVE',
- }),
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error);
- }
-
- const data = await response.json();
- return data.user;
-}
-
-/**
- * 사용자 목록 조회 예제 (필터링)
- */
-export async function getUsersExample() {
- const params = new URLSearchParams({
- role: 'LEARNER',
- status: 'ACTIVE',
- page: '1',
- limit: '10',
- });
-
- const response = await fetch(`/api/users?${params.toString()}`);
- const data = await response.json();
- return data;
-}
-
-// ============================================
-// 2. 교육과정 API 예제
-// ============================================
-
-/**
- * 교육과정 생성 예제
- */
-export async function createCourseExample(instructorId: string) {
- const response = await fetch('/api/courses', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- courseName: '웹 개발 기초',
- instructorId: instructorId,
- createdById: instructorId, // 선택사항
- }),
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error);
- }
-
- const data = await response.json();
- return data.course;
-}
-
-/**
- * 교육과정 목록 조회 예제
- */
-export async function getCoursesExample(instructorId?: string) {
- const params = new URLSearchParams();
- if (instructorId) {
- params.append('instructorId', instructorId);
- }
- params.append('page', '1');
- params.append('limit', '10');
-
- const response = await fetch(`/api/courses?${params.toString()}`);
- const data = await response.json();
- return data;
-}
-
-// ============================================
-// 3. 강좌 API 예제
-// ============================================
-
-/**
- * 강좌 생성 예제
- */
-export async function createLessonExample(courseId: string) {
- const response = await fetch('/api/lessons', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- courseId: courseId,
- lessonName: 'HTML 기초',
- learningGoal: 'HTML의 기본 문법을 이해하고 활용할 수 있다.',
- createdById: undefined, // 선택사항
- }),
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error);
- }
-
- const data = await response.json();
- return data.lesson;
-}
-
-/**
- * 강좌 목록 조회 예제
- */
-export async function getLessonsExample(courseId?: string) {
- const params = new URLSearchParams();
- if (courseId) {
- params.append('courseId', courseId);
- }
- params.append('page', '1');
- params.append('limit', '10');
-
- const response = await fetch(`/api/lessons?${params.toString()}`);
- const data = await response.json();
- return data;
-}
-
-// ============================================
-// 4. 공지사항 API 예제
-// ============================================
-
-/**
- * 공지사항 생성 예제
- */
-export async function createNoticeExample(writerId: string) {
- const response = await fetch('/api/notices', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- title: '공지사항 제목',
- content: '공지사항 내용입니다.\n여러 줄로 작성할 수 있습니다.',
- writerId: writerId,
- hasAttachment: false,
- }),
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error);
- }
-
- const data = await response.json();
- return data.notice;
-}
-
-/**
- * 공지사항 목록 조회 예제
- */
-export async function getNoticesExample(writerId?: string) {
- const params = new URLSearchParams();
- if (writerId) {
- params.append('writerId', writerId);
- }
- params.append('page', '1');
- params.append('limit', '10');
-
- const response = await fetch(`/api/notices?${params.toString()}`);
- const data = await response.json();
- return data;
-}
-
-// ============================================
-// 5. 통합 예제 - 전체 워크플로우
-// ============================================
-
-/**
- * 전체 워크플로우 예제
- * 1. 관리자 사용자 생성
- * 2. 교육과정 생성
- * 3. 강좌 생성
- * 4. 공지사항 생성
- */
-export async function completeWorkflowExample() {
- try {
- // 1. 관리자 사용자 생성
- const adminUser = await createUserExample();
- console.log('관리자 생성:', adminUser);
-
- // 2. 교육과정 생성
- const course = await createCourseExample(adminUser.id);
- console.log('교육과정 생성:', course);
-
- // 3. 강좌 생성
- const lesson = await createLessonExample(course.id);
- console.log('강좌 생성:', lesson);
-
- // 4. 공지사항 생성
- const notice = await createNoticeExample(adminUser.id);
- console.log('공지사항 생성:', notice);
-
- return {
- admin: adminUser,
- course,
- lesson,
- notice,
- };
- } catch (error) {
- console.error('워크플로우 실행 중 오류:', error);
- throw error;
- }
-}
-
-// ============================================
-// 6. 에러 처리 예제
-// ============================================
-
-/**
- * 에러 처리를 포함한 안전한 API 호출 예제
- */
-export async function safeApiCall(
- apiCall: () => Promise
-): Promise<{ data?: T; error?: string }> {
- try {
- const response = await apiCall();
-
- if (!response.ok) {
- const errorData = await response.json();
- return { error: errorData.error || '알 수 없는 오류가 발생했습니다.' };
- }
-
- const data = await response.json();
- return { data: data as T };
- } catch (error) {
- return {
- error: error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.',
- };
- }
-}
-
-// 사용 예시:
-// const result = await safeApiCall(() =>
-// fetch('/api/users', { method: 'POST', ... })
-// );
-// if (result.error) {
-// console.error(result.error);
-// } else {
-// console.log(result.data);
-// }
-
diff --git a/src/app/api/lessons/route.ts b/src/app/api/lessons/route.ts
deleted file mode 100644
index 8bf4010..0000000
--- a/src/app/api/lessons/route.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { prisma } from '@/lib/prisma';
-
-// 강좌 생성
-export async function POST(request: NextRequest) {
- try {
- const body = await request.json();
- const { courseId, lessonName, learningGoal, createdById } = body;
-
- // 필수 필드 검증
- if (!courseId || !lessonName) {
- return NextResponse.json(
- { error: '교육과정 ID와 강좌명은 필수입니다.' },
- { status: 400 }
- );
- }
-
- // 교육과정 존재 확인
- const course = await prisma.course.findUnique({
- where: { id: courseId },
- });
-
- if (!course) {
- return NextResponse.json(
- { error: '교육과정을 찾을 수 없습니다.' },
- { status: 404 }
- );
- }
-
- // 강좌 생성
- const lesson = await prisma.lesson.create({
- data: {
- courseId,
- lessonName,
- learningGoal: learningGoal || null,
- createdById: createdById || course.createdById,
- },
- include: {
- course: {
- select: {
- id: true,
- courseName: true,
- },
- },
- createdBy: {
- select: {
- id: true,
- name: true,
- },
- },
- },
- });
-
- return NextResponse.json(
- { message: '강좌가 성공적으로 생성되었습니다.', lesson },
- { status: 201 }
- );
- } catch (error) {
- console.error('강좌 생성 오류:', error);
- return NextResponse.json(
- { error: '강좌 생성 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
-// 강좌 목록 조회
-export async function GET(request: NextRequest) {
- try {
- const { searchParams } = new URL(request.url);
- const courseId = searchParams.get('courseId');
- const page = parseInt(searchParams.get('page') || '1');
- const limit = parseInt(searchParams.get('limit') || '10');
- const skip = (page - 1) * limit;
-
- const where: any = {};
- if (courseId) where.courseId = courseId;
-
- const [lessons, total] = await Promise.all([
- prisma.lesson.findMany({
- where,
- skip,
- take: limit,
- orderBy: { createdAt: 'desc' },
- include: {
- course: {
- select: {
- id: true,
- courseName: true,
- },
- },
- createdBy: {
- select: {
- id: true,
- name: true,
- },
- },
- _count: {
- select: {
- videos: true,
- vrContents: true,
- questions: true,
- },
- },
- },
- }),
- prisma.lesson.count({ where }),
- ]);
-
- return NextResponse.json({
- lessons,
- pagination: {
- page,
- limit,
- total,
- totalPages: Math.ceil(total / limit),
- },
- });
- } catch (error) {
- console.error('강좌 조회 오류:', error);
- return NextResponse.json(
- { error: '강좌 조회 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
diff --git a/src/app/api/notices/route.ts b/src/app/api/notices/route.ts
deleted file mode 100644
index 544aa02..0000000
--- a/src/app/api/notices/route.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { prisma } from '@/lib/prisma';
-
-// 공지사항 생성
-export async function POST(request: NextRequest) {
- try {
- const body = await request.json();
- const { title, content, writerId, hasAttachment } = body;
-
- // 필수 필드 검증
- if (!title || !content || !writerId) {
- return NextResponse.json(
- { error: '제목, 내용, 작성자 ID는 필수입니다.' },
- { status: 400 }
- );
- }
-
- // 작성자 존재 확인
- const writer = await prisma.user.findUnique({
- where: { id: writerId },
- });
-
- if (!writer) {
- return NextResponse.json(
- { error: '작성자를 찾을 수 없습니다.' },
- { status: 404 }
- );
- }
-
- // 공지사항 생성
- const notice = await prisma.notice.create({
- data: {
- title,
- content,
- writerId,
- hasAttachment: hasAttachment || false,
- },
- include: {
- writer: {
- select: {
- id: true,
- name: true,
- email: true,
- },
- },
- },
- });
-
- return NextResponse.json(
- { message: '공지사항이 성공적으로 생성되었습니다.', notice },
- { status: 201 }
- );
- } catch (error) {
- console.error('공지사항 생성 오류:', error);
- return NextResponse.json(
- { error: '공지사항 생성 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
-// 공지사항 목록 조회
-export async function GET(request: NextRequest) {
- try {
- const { searchParams } = new URL(request.url);
- const writerId = searchParams.get('writerId');
- const page = parseInt(searchParams.get('page') || '1');
- const limit = parseInt(searchParams.get('limit') || '10');
- const skip = (page - 1) * limit;
-
- const where: any = {};
- if (writerId) where.writerId = writerId;
-
- const [notices, total] = await Promise.all([
- prisma.notice.findMany({
- where,
- skip,
- take: limit,
- orderBy: { date: 'desc' },
- include: {
- writer: {
- select: {
- id: true,
- name: true,
- },
- },
- },
- }),
- prisma.notice.count({ where }),
- ]);
-
- return NextResponse.json({
- notices,
- pagination: {
- page,
- limit,
- total,
- totalPages: Math.ceil(total / limit),
- },
- });
- } catch (error) {
- console.error('공지사항 조회 오류:', error);
- return NextResponse.json(
- { error: '공지사항 조회 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts
deleted file mode 100644
index 91bbecb..0000000
--- a/src/app/api/users/route.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { prisma } from '@/lib/prisma';
-import { UserRole, UserStatus } from '@prisma/client';
-
-// 사용자 생성
-export async function POST(request: NextRequest) {
- try {
- const body = await request.json();
- const { email, password, name, phone, gender, birthdate, role, status } = body;
-
- // 필수 필드 검증
- if (!email || !password || !name) {
- return NextResponse.json(
- { error: '이메일, 비밀번호, 이름은 필수입니다.' },
- { status: 400 }
- );
- }
-
- // 이메일 중복 확인
- const existingUser = await prisma.user.findUnique({
- where: { email },
- });
-
- if (existingUser) {
- return NextResponse.json(
- { error: '이미 존재하는 이메일입니다.' },
- { status: 409 }
- );
- }
-
- // 사용자 생성
- const user = await prisma.user.create({
- data: {
- email,
- password, // 실제로는 해시화된 비밀번호를 저장해야 합니다
- name,
- phone: phone || null,
- gender: gender || null,
- birthdate: birthdate ? new Date(birthdate) : null,
- role: role || UserRole.LEARNER,
- status: status || UserStatus.ACTIVE,
- },
- });
-
- // 비밀번호 제외하고 반환
- const { password: _, ...userWithoutPassword } = user;
-
- return NextResponse.json(
- { message: '사용자가 성공적으로 생성되었습니다.', user: userWithoutPassword },
- { status: 201 }
- );
- } catch (error) {
- console.error('사용자 생성 오류:', error);
- return NextResponse.json(
- { error: '사용자 생성 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
-// 사용자 목록 조회
-export async function GET(request: NextRequest) {
- try {
- const { searchParams } = new URL(request.url);
- const role = searchParams.get('role');
- const status = searchParams.get('status');
- const page = parseInt(searchParams.get('page') || '1');
- const limit = parseInt(searchParams.get('limit') || '10');
- const skip = (page - 1) * limit;
-
- const where: any = {};
- if (role) where.role = role;
- if (status) where.status = status;
-
- const [users, total] = await Promise.all([
- prisma.user.findMany({
- where,
- skip,
- take: limit,
- orderBy: { createdAt: 'desc' },
- select: {
- id: true,
- email: true,
- name: true,
- phone: true,
- gender: true,
- birthdate: true,
- role: true,
- status: true,
- joinDate: true,
- createdAt: true,
- updatedAt: true,
- },
- }),
- prisma.user.count({ where }),
- ]);
-
- return NextResponse.json({
- users,
- pagination: {
- page,
- limit,
- total,
- totalPages: Math.ceil(total / limit),
- },
- });
- } catch (error) {
- console.error('사용자 조회 오류:', error);
- return NextResponse.json(
- { error: '사용자 조회 중 오류가 발생했습니다.' },
- { status: 500 }
- );
- }
-}
-
diff --git a/src/app/find-id/page.tsx b/src/app/find-id/page.tsx
index 25c81c1..e5a224b 100644
--- a/src/app/find-id/page.tsx
+++ b/src/app/find-id/page.tsx
@@ -27,12 +27,36 @@ export default function FindIdPage() {
return Object.keys(next).length === 0;
}
- function handleSubmit(e: React.FormEvent) {
+ async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!validateAll()) return;
- const mockUserId = `${name.trim()}@example.com`;
- setFoundUserId(mockUserId);
- setIsDoneOpen(true);
+
+ try {
+ const response = await fetch('https://hrdi.coconutmeet.net/auth/find-id', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ name: name,
+ phone: phone,
+ }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ console.error('아이디 찾기 실패:', errorData.error || response.statusText);
+ setIsFailedOpen(true);
+ return;
+ }
+
+ const data = await response.json();
+ setFoundUserId(data.email);
+ setIsDoneOpen(true);
+ } catch (error) {
+ console.error('아이디 찾기 오류:', error);
+ setIsFailedOpen(true);
+ }
}
return (
diff --git a/src/app/globals.css b/src/app/globals.css
index 8541e68..f2cdd58 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -41,3 +41,11 @@ body {
background: var(--background);
color: var(--foreground);
}
+
+button {
+ cursor: pointer;
+}
+
+button:hover {
+ cursor: pointer;
+}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index 1c4c529..7ae5cab 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -20,13 +20,57 @@ export default function LoginPage() {
const [idError, setIdError] = useState("");
const [passwordError, setPasswordError] = useState("");
- function handleSubmit(e: React.FormEvent) {
+ async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
- // 실제 로그인 API 연동 전까지는 실패 모달을 노출합니다.
- // API 연동 시 결과에 따라 성공/실패 분기에서 setIsLoginErrorOpen(true) 호출로 교체하세요.
- // if (userId.trim().length > 0 && password.trim().length > 0) {
- // setIsLoginErrorOpen(true);
- // }
+ if (userId.trim().length === 0 || password.trim().length === 0) {
+ return;
+ }
+
+ try {
+ const response = await fetch("https://hrdi.coconutmeet.net/auth/login", {
+ method: "POST",
+ headers: {"Content-Type": "application/json",},
+ body: JSON.stringify({
+ email: userId,
+ password: password
+ })
+ });
+
+ if (!response.ok) {
+ let errorMessage = `로그인 실패 (${response.status})`;
+ try {
+ const errorData = await response.json();
+ if (errorData.error) {
+ errorMessage = errorData.error;
+ } else if (errorData.message) {
+ errorMessage = errorData.message;
+ } else if (response.statusText) {
+ errorMessage = `${response.statusText} (${response.status})`;
+ }
+ } catch (parseError) {
+ if (response.statusText) {
+ errorMessage = `${response.statusText} (${response.status})`;
+ }
+ }
+ console.error("로그인 실패:", errorMessage);
+ setIsLoginErrorOpen(true);
+ return;
+ }
+
+ const data = await response.json();
+ console.log("로그인 성공:", data);
+
+ // 로그인 성공 시 처리 (예: 토큰 저장, 리다이렉트 등)
+ // TODO: 성공 시 처리 로직 추가 (예: localStorage에 토큰 저장, 메인 페이지로 이동 등)
+ // if (data.token) {
+ // localStorage.setItem('token', data.token);
+ // window.location.href = '/menu';
+ // }
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : "네트워크 오류가 발생했습니다.";
+ console.error("로그인 오류:", errorMessage);
+ setIsLoginErrorOpen(true);
+ }
}
return (
@@ -106,12 +150,12 @@ export default function LoginPage() {
onBlur={() => setIsPasswordFocused(false)}
placeholder="비밀번호"
className="
- h-[40px] px-[12px] py-[7px] rounded-[8px] w-full border border-neutral-40
- focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
- focus:appearance-none focus:border-neutral-700
- text-[18px] text-neutral-700 font-normal leading-[150%] placeholder:text-input-placeholder-text
- pr-[40px]
- "
+ h-[40px] px-[12px] py-[7px] rounded-[8px] w-full border border-neutral-40
+ focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
+ focus:appearance-none focus:border-neutral-700
+ text-[18px] text-neutral-700 font-normal leading-[150%] placeholder:text-input-placeholder-text
+ pr-[40px]
+ "
/>
{password.trim().length > 0 && isPasswordFocused && (