프리즈마 세팅팅
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -39,3 +39,5 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
/lib/generated/prisma
|
||||
|
||||
161
app/api/curriculums/route.ts
Normal file
161
app/api/curriculums/route.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
220
app/api/lectures/route.ts
Normal file
220
app/api/lectures/route.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
234
app/api/user-lectures/route.ts
Normal file
234
app/api/user-lectures/route.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
48
app/api/users/login/route.ts
Normal file
48
app/api/users/login/route.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
187
app/api/users/route.ts
Normal file
187
app/api/users/route.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
14
lib/prisma.ts
Normal file
14
lib/prisma.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
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;
|
||||
|
||||
411
package-lock.json
generated
411
package-lock.json
generated
@@ -8,9 +8,14 @@
|
||||
"name": "xr_lms",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.19.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"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"
|
||||
},
|
||||
@@ -1190,6 +1195,85 @@
|
||||
"node": ">=12.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz",
|
||||
"integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prisma": "*",
|
||||
"typescript": ">=5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"prisma": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "6.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz",
|
||||
"integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"c12": "3.1.0",
|
||||
"deepmerge-ts": "7.1.5",
|
||||
"effect": "3.18.4",
|
||||
"empathic": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "6.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz",
|
||||
"integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==",
|
||||
"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==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.19.0",
|
||||
"@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773",
|
||||
"@prisma/fetch-engine": "6.19.0",
|
||||
"@prisma/get-platform": "6.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"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==",
|
||||
"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==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.19.0",
|
||||
"@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773",
|
||||
"@prisma/get-platform": "6.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "6.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz",
|
||||
"integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rtsao/scc": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||
@@ -1197,6 +1281,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@@ -1488,6 +1578,12 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bcryptjs": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
|
||||
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/better-sqlite3": {
|
||||
"version": "7.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
||||
@@ -2440,6 +2536,15 @@
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
|
||||
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"bin": {
|
||||
"bcrypt": "bin/bcrypt"
|
||||
}
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "12.4.1",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.1.tgz",
|
||||
@@ -2556,6 +2661,46 @@
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
|
||||
"integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
"confbox": "^0.2.2",
|
||||
"defu": "^6.1.4",
|
||||
"dotenv": "^16.6.1",
|
||||
"exsolve": "^1.0.7",
|
||||
"giget": "^2.0.0",
|
||||
"jiti": "^2.4.2",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^2.2.0",
|
||||
"rc9": "^2.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"magicast": "^0.3.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"magicast": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||
@@ -2653,12 +2798,36 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/citty": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"consola": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
@@ -2692,6 +2861,21 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
|
||||
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@@ -2831,6 +3015,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge-ts": {
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
||||
"integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
@@ -2867,6 +3060,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
@@ -2889,6 +3094,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -2904,6 +3121,16 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "3.18.4",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
|
||||
"integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"fast-check": "^3.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.243",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.243.tgz",
|
||||
@@ -2918,6 +3145,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/empathic": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
|
||||
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
@@ -3574,6 +3810,34 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
|
||||
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||
"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==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pure-rand": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -3858,6 +4122,23 @@
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
"node-fetch-native": "^1.6.6",
|
||||
"nypm": "^0.6.0",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"bin": {
|
||||
"giget": "dist/cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
@@ -4580,7 +4861,6 @@
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
@@ -5262,6 +5542,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch-native": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
|
||||
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.26",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
|
||||
@@ -5269,6 +5555,25 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.2",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"nypm": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -5392,6 +5697,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ohash": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -5509,6 +5820,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -5528,6 +5851,17 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.2",
|
||||
"exsolve": "^1.0.7",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -5603,6 +5937,31 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "6.19.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz",
|
||||
"integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.19.0",
|
||||
"@prisma/engines": "6.19.0"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -5635,6 +5994,22 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -5680,6 +6055,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rc9": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
@@ -5722,6 +6107,19 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||
@@ -6455,6 +6853,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
||||
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
@@ -6668,7 +7075,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
||||
11
package.json
11
package.json
@@ -6,12 +6,21 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
"lint": "eslint",
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.19.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"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"
|
||||
},
|
||||
|
||||
24
prisma.config.ts
Normal file
24
prisma.config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
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";
|
||||
|
||||
export default defineConfig({
|
||||
schema: "prisma/schema.prisma",
|
||||
migrations: {
|
||||
path: "prisma/migrations",
|
||||
},
|
||||
engine: "classic",
|
||||
datasource: {
|
||||
url: databaseUrl,
|
||||
},
|
||||
});
|
||||
BIN
prisma/dev.db
Normal file
BIN
prisma/dev.db
Normal file
Binary file not shown.
91
prisma/schema.prisma
Normal file
91
prisma/schema.prisma
Normal file
@@ -0,0 +1,91 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../lib/generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user