diff --git a/.gitignore b/.gitignore index 62fcee8..cd3f340 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,9 @@ yarn-error.log* next-env.d.ts /lib/generated/prisma + +/lib/generated/prisma + +/lib/generated/prisma + +/lib/generated/prisma diff --git a/app/api/curriculums/route.ts b/app/api/curriculums/route.ts deleted file mode 100644 index dd6f012..0000000 --- a/app/api/curriculums/route.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { prisma } from '@/lib/prisma'; - -// GET - 교육 과정 목록 조회 또는 단일 교육 과정 조회 -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - const instructorId = searchParams.get('instructorId'); - - if (id) { - // 단일 교육 과정 조회 - const curriculum = await prisma.curriculum.findUnique({ - where: { id }, - include: { - lectures: { - include: { - registrant: { - select: { - id: true, - name: true, - email: true, - }, - }, - }, - }, - }, - }); - - if (!curriculum) { - return NextResponse.json({ error: '교육 과정을 찾을 수 없습니다.' }, { status: 404 }); - } - - return NextResponse.json(curriculum); - } - - // 전체 교육 과정 목록 조회 - let where: any = {}; - if (instructorId) { - where.instructorId = instructorId; - } - - const curriculums = await prisma.curriculum.findMany({ - where, - include: { - lectures: { - select: { - id: true, - title: true, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }); - - return NextResponse.json(curriculums); - } catch (error) { - console.error('Error fetching curriculums:', error); - return NextResponse.json({ error: '교육 과정 조회 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// POST - 교육 과정 생성 -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { title, instructorId } = body; - - if (!title || !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 curriculum = await prisma.curriculum.create({ - data: { - title, - instructorId, - }, - include: { - lectures: true, - }, - }); - - return NextResponse.json(curriculum, { status: 201 }); - } catch (error) { - console.error('Error creating curriculum:', error); - return NextResponse.json({ error: '교육 과정 생성 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// PUT - 교육 과정 수정 -export async function PUT(request: NextRequest) { - try { - const body = await request.json(); - const { id, title, instructorId } = body; - - if (!id) { - return NextResponse.json({ error: '교육 과정 ID는 필수입니다.' }, { status: 400 }); - } - - const updateData: any = {}; - if (title !== undefined) updateData.title = title; - if (instructorId !== undefined) { - // 강사 존재 확인 - const instructor = await prisma.user.findUnique({ - where: { id: instructorId }, - }); - - if (!instructor) { - return NextResponse.json({ error: '강사를 찾을 수 없습니다.' }, { status: 404 }); - } - - updateData.instructorId = instructorId; - } - - const curriculum = await prisma.curriculum.update({ - where: { id }, - data: updateData, - include: { - lectures: true, - }, - }); - - return NextResponse.json(curriculum); - } catch (error) { - console.error('Error updating curriculum:', error); - return NextResponse.json({ error: '교육 과정 수정 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// DELETE - 교육 과정 삭제 -export async function DELETE(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - - if (!id) { - return NextResponse.json({ error: '교육 과정 ID는 필수입니다.' }, { status: 400 }); - } - - await prisma.curriculum.delete({ - where: { id }, - }); - - return NextResponse.json({ message: '교육 과정이 삭제되었습니다.' }); - } catch (error) { - console.error('Error deleting curriculum:', error); - return NextResponse.json({ error: '교육 과정 삭제 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - diff --git a/app/api/lectures/route.ts b/app/api/lectures/route.ts deleted file mode 100644 index d0b3762..0000000 --- a/app/api/lectures/route.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { prisma } from '@/lib/prisma'; - -// GET - 강좌 목록 조회 또는 단일 강좌 조회 -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - const curriculumId = searchParams.get('curriculumId'); - const registrantId = searchParams.get('registrantId'); - - if (id) { - // 단일 강좌 조회 - const lecture = await prisma.lecture.findUnique({ - where: { id }, - include: { - curriculum: true, - registrant: { - select: { - id: true, - name: true, - email: true, - }, - }, - enrolledUsers: { - include: { - user: { - select: { - id: true, - name: true, - email: true, - }, - }, - }, - }, - }, - }); - - if (!lecture) { - return NextResponse.json({ error: '강좌를 찾을 수 없습니다.' }, { status: 404 }); - } - - return NextResponse.json(lecture); - } - - // 전체 강좌 목록 조회 - let where: any = {}; - if (curriculumId) { - where.curriculumId = curriculumId; - } - if (registrantId) { - where.registrantId = registrantId; - } - - const lectures = await prisma.lecture.findMany({ - where, - include: { - curriculum: { - select: { - id: true, - title: true, - }, - }, - registrant: { - select: { - id: true, - name: true, - email: true, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }); - - return NextResponse.json(lectures); - } catch (error) { - console.error('Error fetching lectures:', error); - return NextResponse.json({ error: '강좌 조회 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// POST - 강좌 생성 -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { title, attachmentFile, evaluationQuestionCount, curriculumId, registrantId } = body; - - if (!title || !curriculumId || !registrantId) { - return NextResponse.json({ error: '강좌명, 교육 과정 ID, 등록자 ID는 필수입니다.' }, { status: 400 }); - } - - // 교육 과정 존재 확인 - const curriculum = await prisma.curriculum.findUnique({ - where: { id: curriculumId }, - }); - - if (!curriculum) { - return NextResponse.json({ error: '교육 과정을 찾을 수 없습니다.' }, { status: 404 }); - } - - // 등록자 존재 확인 - const registrant = await prisma.user.findUnique({ - where: { id: registrantId }, - }); - - if (!registrant) { - return NextResponse.json({ error: '등록자를 찾을 수 없습니다.' }, { status: 404 }); - } - - const lecture = await prisma.lecture.create({ - data: { - title, - attachmentFile: attachmentFile || null, - evaluationQuestionCount: evaluationQuestionCount || 0, - curriculumId, - registrantId, - }, - include: { - curriculum: true, - registrant: { - select: { - id: true, - name: true, - email: true, - }, - }, - }, - }); - - return NextResponse.json(lecture, { status: 201 }); - } catch (error) { - console.error('Error creating lecture:', error); - return NextResponse.json({ error: '강좌 생성 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// PUT - 강좌 수정 -export async function PUT(request: NextRequest) { - try { - const body = await request.json(); - const { id, title, attachmentFile, evaluationQuestionCount, curriculumId, registrantId } = body; - - if (!id) { - return NextResponse.json({ error: '강좌 ID는 필수입니다.' }, { status: 400 }); - } - - const updateData: any = {}; - if (title !== undefined) updateData.title = title; - if (attachmentFile !== undefined) updateData.attachmentFile = attachmentFile; - if (evaluationQuestionCount !== undefined) updateData.evaluationQuestionCount = parseInt(evaluationQuestionCount); - if (curriculumId !== undefined) { - // 교육 과정 존재 확인 - const curriculum = await prisma.curriculum.findUnique({ - where: { id: curriculumId }, - }); - - if (!curriculum) { - return NextResponse.json({ error: '교육 과정을 찾을 수 없습니다.' }, { status: 404 }); - } - - updateData.curriculumId = curriculumId; - } - if (registrantId !== undefined) { - // 등록자 존재 확인 - const registrant = await prisma.user.findUnique({ - where: { id: registrantId }, - }); - - if (!registrant) { - return NextResponse.json({ error: '등록자를 찾을 수 없습니다.' }, { status: 404 }); - } - - updateData.registrantId = registrantId; - } - - const lecture = await prisma.lecture.update({ - where: { id }, - data: updateData, - include: { - curriculum: true, - registrant: { - select: { - id: true, - name: true, - email: true, - }, - }, - }, - }); - - return NextResponse.json(lecture); - } catch (error) { - console.error('Error updating lecture:', error); - return NextResponse.json({ error: '강좌 수정 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// DELETE - 강좌 삭제 -export async function DELETE(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - - if (!id) { - return NextResponse.json({ error: '강좌 ID는 필수입니다.' }, { status: 400 }); - } - - await prisma.lecture.delete({ - where: { id }, - }); - - return NextResponse.json({ message: '강좌가 삭제되었습니다.' }); - } catch (error) { - console.error('Error deleting lecture:', error); - return NextResponse.json({ error: '강좌 삭제 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - diff --git a/app/api/user-lectures/route.ts b/app/api/user-lectures/route.ts deleted file mode 100644 index c89f160..0000000 --- a/app/api/user-lectures/route.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { prisma } from '@/lib/prisma'; - -// GET - 수강 목록 조회 또는 단일 수강 조회 -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - const userId = searchParams.get('userId'); - const lectureId = searchParams.get('lectureId'); - - if (id) { - // 단일 수강 조회 - const userLecture = await prisma.userLecture.findUnique({ - where: { id }, - include: { - user: { - select: { - id: true, - name: true, - email: true, - }, - }, - lecture: { - include: { - curriculum: true, - }, - }, - }, - }); - - if (!userLecture) { - return NextResponse.json({ error: '수강 정보를 찾을 수 없습니다.' }, { status: 404 }); - } - - return NextResponse.json(userLecture); - } - - // 수강 목록 조회 - let where: any = {}; - if (userId) { - where.userId = userId; - } - if (lectureId) { - where.lectureId = lectureId; - } - - const userLectures = await prisma.userLecture.findMany({ - where, - include: { - user: { - select: { - id: true, - name: true, - email: true, - }, - }, - lecture: { - include: { - curriculum: { - select: { - id: true, - title: true, - }, - }, - }, - }, - }, - orderBy: { - enrolledAt: 'desc', - }, - }); - - return NextResponse.json(userLectures); - } catch (error) { - console.error('Error fetching user lectures:', error); - return NextResponse.json({ error: '수강 정보 조회 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// POST - 수강 등록 -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { userId, lectureId } = body; - - if (!userId || !lectureId) { - return NextResponse.json({ error: '사용자 ID와 강좌 ID는 필수입니다.' }, { status: 400 }); - } - - // 사용자 존재 확인 - const user = await prisma.user.findUnique({ - where: { id: userId }, - }); - - if (!user) { - return NextResponse.json({ error: '사용자를 찾을 수 없습니다.' }, { status: 404 }); - } - - // 강좌 존재 확인 - const lecture = await prisma.lecture.findUnique({ - where: { id: lectureId }, - }); - - if (!lecture) { - return NextResponse.json({ error: '강좌를 찾을 수 없습니다.' }, { status: 404 }); - } - - // 이미 수강 중인지 확인 - const existingEnrollment = await prisma.userLecture.findUnique({ - where: { - userId_lectureId: { - userId, - lectureId, - }, - }, - }); - - if (existingEnrollment) { - return NextResponse.json({ error: '이미 수강 중인 강좌입니다.' }, { status: 409 }); - } - - // 수강 등록 - const userLecture = await prisma.userLecture.create({ - data: { - userId, - lectureId, - progress: 0, - isCompleted: false, - }, - include: { - user: { - select: { - id: true, - name: true, - email: true, - }, - }, - lecture: { - include: { - curriculum: true, - }, - }, - }, - }); - - return NextResponse.json(userLecture, { status: 201 }); - } catch (error) { - console.error('Error enrolling lecture:', error); - return NextResponse.json({ error: '수강 등록 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// PUT - 수강 정보 수정 (진행률, 완료 여부 등) -export async function PUT(request: NextRequest) { - try { - const body = await request.json(); - const { id, progress, isCompleted, score } = body; - - if (!id) { - return NextResponse.json({ error: '수강 ID는 필수입니다.' }, { status: 400 }); - } - - const updateData: any = {}; - if (progress !== undefined) { - const progressValue = parseInt(progress); - if (progressValue < 0 || progressValue > 100) { - return NextResponse.json({ error: '진행률은 0-100 사이여야 합니다.' }, { status: 400 }); - } - updateData.progress = progressValue; - } - if (isCompleted !== undefined) { - updateData.isCompleted = isCompleted; - if (isCompleted && !updateData.completedAt) { - updateData.completedAt = new Date(); - } else if (!isCompleted) { - updateData.completedAt = null; - } - } - if (score !== undefined) { - const scoreValue = parseInt(score); - if (scoreValue < 0 || scoreValue > 100) { - return NextResponse.json({ error: '점수는 0-100 사이여야 합니다.' }, { status: 400 }); - } - updateData.score = scoreValue; - } - - const userLecture = await prisma.userLecture.update({ - where: { id }, - data: updateData, - include: { - user: { - select: { - id: true, - name: true, - email: true, - }, - }, - lecture: { - include: { - curriculum: true, - }, - }, - }, - }); - - return NextResponse.json(userLecture); - } catch (error) { - console.error('Error updating user lecture:', error); - return NextResponse.json({ error: '수강 정보 수정 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// DELETE - 수강 취소 -export async function DELETE(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - - if (!id) { - return NextResponse.json({ error: '수강 ID는 필수입니다.' }, { status: 400 }); - } - - await prisma.userLecture.delete({ - where: { id }, - }); - - return NextResponse.json({ message: '수강이 취소되었습니다.' }); - } catch (error) { - console.error('Error deleting user lecture:', error); - return NextResponse.json({ error: '수강 취소 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - diff --git a/app/api/users/login/route.ts b/app/api/users/login/route.ts deleted file mode 100644 index 39f4a4c..0000000 --- a/app/api/users/login/route.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { prisma } from '@/lib/prisma'; -import bcrypt from 'bcryptjs'; - -// POST - 로그인 -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { email, password } = body; - - if (!email || !password) { - return NextResponse.json({ error: '이메일과 비밀번호를 입력해주세요.' }, { status: 400 }); - } - - // 사용자 조회 - const user = await prisma.user.findUnique({ - where: { email }, - }); - - if (!user) { - return NextResponse.json({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' }, { status: 401 }); - } - - // 계정 활성화 확인 - if (!user.isActive) { - return NextResponse.json({ error: '비활성화된 계정입니다.' }, { status: 403 }); - } - - // 비밀번호 확인 - const isPasswordValid = await bcrypt.compare(password, user.password); - - if (!isPasswordValid) { - return NextResponse.json({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' }, { status: 401 }); - } - - // 비밀번호 제외하고 사용자 정보 반환 - const { password: _, ...userWithoutPassword } = user; - - return NextResponse.json({ - user: userWithoutPassword, - message: '로그인 성공', - }); - } catch (error) { - console.error('Error during login:', error); - return NextResponse.json({ error: '로그인 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - diff --git a/app/api/users/route.ts b/app/api/users/route.ts deleted file mode 100644 index 8dfe143..0000000 --- a/app/api/users/route.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { prisma } from '@/lib/prisma'; -import bcrypt from 'bcryptjs'; - -// GET - 사용자 목록 조회 또는 단일 사용자 조회 -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - const email = searchParams.get('email'); - - if (id) { - // 단일 사용자 조회 - const user = await prisma.user.findUnique({ - where: { id }, - include: { - enrolledLectures: { - include: { - lecture: true, - }, - }, - }, - }); - - if (!user) { - return NextResponse.json({ error: '사용자를 찾을 수 없습니다.' }, { status: 404 }); - } - - // 비밀번호 제외 - const { password, ...userWithoutPassword } = user; - return NextResponse.json(userWithoutPassword); - } - - if (email) { - // 이메일로 사용자 조회 - const user = await prisma.user.findUnique({ - where: { email }, - }); - - if (!user) { - return NextResponse.json({ error: '사용자를 찾을 수 없습니다.' }, { status: 404 }); - } - - const { password, ...userWithoutPassword } = user; - return NextResponse.json(userWithoutPassword); - } - - // 전체 사용자 목록 조회 - const users = await prisma.user.findMany({ - select: { - id: true, - email: true, - name: true, - phone: true, - gender: true, - role: true, - isActive: true, - createdAt: true, - updatedAt: true, - }, - }); - - return NextResponse.json(users); - } catch (error) { - console.error('Error fetching users:', error); - return NextResponse.json({ error: '사용자 조회 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// POST - 사용자 생성 (회원가입) -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { email, password, name, phone, gender, birthYear, birthMonth, birthDay, role } = body; - - // 필수 필드 검증 - if (!email || !password) { - return NextResponse.json({ error: '이메일과 비밀번호는 필수입니다.' }, { status: 400 }); - } - - // 이메일 중복 확인 - const existingUser = await prisma.user.findUnique({ - where: { email }, - }); - - if (existingUser) { - return NextResponse.json({ error: '이미 등록된 이메일입니다.' }, { status: 409 }); - } - - // 비밀번호 해시화 - const hashedPassword = await bcrypt.hash(password, 10); - - // 사용자 생성 - const user = await prisma.user.create({ - data: { - email, - password: hashedPassword, - name: name || null, - phone: phone || null, - gender: gender || null, - birthYear: birthYear ? parseInt(birthYear) : null, - birthMonth: birthMonth ? parseInt(birthMonth) : null, - birthDay: birthDay ? parseInt(birthDay) : null, - role: role || 'STUDENT', - }, - select: { - id: true, - email: true, - name: true, - phone: true, - gender: true, - role: true, - isActive: true, - createdAt: true, - }, - }); - - return NextResponse.json(user, { status: 201 }); - } catch (error) { - console.error('Error creating user:', error); - return NextResponse.json({ error: '사용자 생성 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// PUT - 사용자 정보 수정 -export async function PUT(request: NextRequest) { - try { - const body = await request.json(); - const { id, name, phone, gender, birthYear, birthMonth, birthDay, role, isActive } = body; - - if (!id) { - return NextResponse.json({ error: '사용자 ID는 필수입니다.' }, { status: 400 }); - } - - const updateData: any = {}; - if (name !== undefined) updateData.name = name; - if (phone !== undefined) updateData.phone = phone; - if (gender !== undefined) updateData.gender = gender; - if (birthYear !== undefined) updateData.birthYear = birthYear ? parseInt(birthYear) : null; - if (birthMonth !== undefined) updateData.birthMonth = birthMonth ? parseInt(birthMonth) : null; - if (birthDay !== undefined) updateData.birthDay = birthDay ? parseInt(birthDay) : null; - if (role !== undefined) updateData.role = role; - if (isActive !== undefined) updateData.isActive = isActive; - - const user = await prisma.user.update({ - where: { id }, - data: updateData, - select: { - id: true, - email: true, - name: true, - phone: true, - gender: true, - role: true, - isActive: true, - updatedAt: true, - }, - }); - - return NextResponse.json(user); - } catch (error) { - console.error('Error updating user:', error); - return NextResponse.json({ error: '사용자 정보 수정 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - -// DELETE - 사용자 삭제 -export async function DELETE(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const id = searchParams.get('id'); - - if (!id) { - return NextResponse.json({ error: '사용자 ID는 필수입니다.' }, { status: 400 }); - } - - await prisma.user.delete({ - where: { id }, - }); - - return NextResponse.json({ message: '사용자가 삭제되었습니다.' }); - } catch (error) { - console.error('Error deleting user:', error); - return NextResponse.json({ error: '사용자 삭제 중 오류가 발생했습니다.' }, { status: 500 }); - } -} - diff --git a/lib/prisma.ts b/lib/prisma.ts deleted file mode 100644 index eb5d383..0000000 --- a/lib/prisma.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PrismaClient } from './generated/prisma'; - -const globalForPrisma = globalThis as unknown as { - prisma: PrismaClient | undefined; -}; - -export const prisma = - globalForPrisma.prisma ?? - new PrismaClient({ - log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], - }); - -if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; - diff --git a/package-lock.json b/package-lock.json index 999e1f9..52ea213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "better-sqlite3": "^12.4.1", "dotenv": "^17.2.3", "next": "16.0.1", - "prisma": "^6.19.0", "react": "19.2.0", "react-dom": "19.2.0" }, @@ -26,6 +25,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.0.1", + "prisma": "^6.19.0", "tailwindcss": "^4", "typescript": "5.9.3" } @@ -1221,6 +1221,7 @@ "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", @@ -1233,12 +1234,14 @@ "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1252,12 +1255,14 @@ "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.19.0", @@ -1269,6 +1274,7 @@ "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.19.0" @@ -1285,6 +1291,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true, "license": "MIT" }, "node_modules/@swc/helpers": { @@ -2665,6 +2672,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.3", @@ -2693,6 +2701,7 @@ "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2802,6 +2811,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2823,6 +2833,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, "license": "MIT", "dependencies": { "consola": "^3.2.3" @@ -2865,12 +2876,14 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -3019,6 +3032,7 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" @@ -3064,12 +3078,14 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, "license": "MIT" }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, "license": "MIT" }, "node_modules/detect-libc": { @@ -3125,6 +3141,7 @@ "version": "3.18.4", "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -3149,6 +3166,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=14" @@ -3814,12 +3832,14 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, "license": "MIT" }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, "funding": [ { "type": "individual", @@ -4126,6 +4146,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -4861,6 +4882,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -5546,6 +5568,7 @@ "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, "license": "MIT" }, "node_modules/node-releases": { @@ -5559,6 +5582,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -5701,6 +5725,7 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, "license": "MIT" }, "node_modules/once": { @@ -5824,12 +5849,14 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, "license": "MIT" }, "node_modules/picocolors": { @@ -5855,6 +5882,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, "license": "MIT", "dependencies": { "confbox": "^0.2.2", @@ -5941,6 +5969,7 @@ "version": "6.19.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -5998,6 +6027,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, "funding": [ { "type": "individual", @@ -6059,6 +6089,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, "license": "MIT", "dependencies": { "defu": "^6.1.4", @@ -6111,6 +6142,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -6857,6 +6889,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" diff --git a/package.json b/package.json index fa5a56a..a9d3eb4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "db:generate": "prisma generate", "db:push": "prisma db push", "db:migrate": "prisma migrate dev", - "db:studio": "prisma studio" + "db:studio": "prisma studio", + "db:seed": "tsx prisma/seed.ts" + }, + "prisma": { + "seed": "tsx prisma/seed.ts" }, "dependencies": { "@prisma/client": "^6.19.0", @@ -20,7 +24,6 @@ "better-sqlite3": "^12.4.1", "dotenv": "^17.2.3", "next": "16.0.1", - "prisma": "^6.19.0", "react": "19.2.0", "react-dom": "19.2.0" }, @@ -31,7 +34,9 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.0.1", + "prisma": "^6.19.0", "tailwindcss": "^4", + "tsx": "^4.19.2", "typescript": "5.9.3" } -} +} \ No newline at end of file diff --git a/prisma.config.ts b/prisma.config.ts index 08d49c7..6b6d3b6 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -1,16 +1,4 @@ -import { config } from "dotenv"; -import { defineConfig } from "prisma/config"; -import { resolve } from "path"; -import { existsSync } from "fs"; - -// .env 파일을 명시적으로 로드 -const envPath = resolve(process.cwd(), ".env"); -if (existsSync(envPath)) { - config({ path: envPath }); -} - -// .env 파일에서 DATABASE_URL을 읽어옵니다 (기본값: file:./dev.db) -const databaseUrl = process.env.DATABASE_URL || "file:./dev.db"; +import { defineConfig, env } from "prisma/config"; export default defineConfig({ schema: "prisma/schema.prisma", @@ -19,6 +7,6 @@ export default defineConfig({ }, engine: "classic", datasource: { - url: databaseUrl, + url: env("DATABASE_URL"), }, }); diff --git a/prisma/dev.db b/prisma/dev.db deleted file mode 100644 index 0fc148b..0000000 Binary files a/prisma/dev.db and /dev/null differ diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d6e5ed4..9cdce9f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,9 @@ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + generator client { provider = "prisma-client" output = "../lib/generated/prisma" @@ -11,81 +14,9 @@ datasource db { url = env("DATABASE_URL") } -// 사용자 권한 Enum -enum UserRole { - STUDENT // 학습자 - INSTRUCTOR // 강사 - ADMIN // 관리자 -} - -// User 모델 -model User { - id String @id @default(cuid()) - email String @unique - name String? - password String - phone String? - gender String? - birthYear Int? - birthMonth Int? - birthDay Int? - role UserRole @default(STUDENT) // 권한 (기본값: 학습자) - isActive Boolean @default(true) // 계정 활성화 여부 (true: 활성화, false: 비활성화) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - registeredLectures Lecture[] @relation("Registrant") // 등록한 강좌 목록 - enrolledLectures UserLecture[] // 수강 중인 강좌 목록 - - @@map("users") -} - -// 교육 과정 관리 -model Curriculum { - id String @id @default(cuid()) - title String // 과정 제목 - instructorId String // 강사 ID (User와의 관계) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - lectures Lecture[] // 강좌 목록 (1:N 관계) - - @@map("curriculums") -} - -// 강좌 관리 -model Lecture { - id String @id @default(cuid()) - title String // 강좌명 - attachmentFile String? // 첨부 파일 경로 - evaluationQuestionCount Int @default(0) // 학습 평가 문제 수 - curriculumId String // 교육 과정 ID (Curriculum과의 관계) - registrantId String // 등록자 ID (User와의 관계) - registeredAt DateTime @default(now()) // 등록일 - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - curriculum Curriculum @relation(fields: [curriculumId], references: [id], onDelete: Cascade) - registrant User @relation("Registrant", fields: [registrantId], references: [id]) - enrolledUsers UserLecture[] // 수강 중인 사용자 목록 - - @@map("lectures") -} - -// 사용자-강좌 수강 관계 (다대다 관계) -model UserLecture { - id String @id @default(cuid()) - userId String // 사용자 ID - lectureId String // 강좌 ID - enrolledAt DateTime @default(now()) // 수강 시작일 - completedAt DateTime? // 수강 완료일 - isCompleted Boolean @default(false) // 수강 완료 여부 - progress Int @default(0) // 수강 진행률 (0-100) - score Int? // 평가 점수 - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - lecture Lecture @relation(fields: [lectureId], references: [id], onDelete: Cascade) - - @@unique([userId, lectureId]) // 한 사용자는 같은 강좌를 중복 수강할 수 없음 - @@map("user_lectures") -} +model Test { + id String @id @default(uuid()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} \ No newline at end of file