prizma 이상한거거

This commit is contained in:
wallace
2025-11-11 10:42:56 +09:00
parent 512acfb921
commit 218e915e37
12 changed files with 59 additions and 960 deletions

6
.gitignore vendored
View File

@@ -41,3 +41,9 @@ yarn-error.log*
next-env.d.ts
/lib/generated/prisma
/lib/generated/prisma
/lib/generated/prisma
/lib/generated/prisma

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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;

35
package-lock.json generated
View File

@@ -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"

View File

@@ -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"
}
}

View File

@@ -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"),
},
});

Binary file not shown.

View File

@@ -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: 비활성화)
model Test {
id String @id @default(uuid())
name String
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")
}