프리즈마 세팅팅

This commit is contained in:
wallace
2025-11-10 21:57:28 +09:00
parent 9a11a8afd1
commit 512acfb921
12 changed files with 1400 additions and 3 deletions

2
.gitignore vendored
View File

@@ -39,3 +39,5 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
/lib/generated/prisma

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

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

View 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
View 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
View 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
View File

@@ -8,9 +8,14 @@
"name": "xr_lms", "name": "xr_lms",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@prisma/client": "^6.19.0",
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.4.1", "better-sqlite3": "^12.4.1",
"dotenv": "^17.2.3",
"next": "16.0.1", "next": "16.0.1",
"prisma": "^6.19.0",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0" "react-dom": "19.2.0"
}, },
@@ -1190,6 +1195,85 @@
"node": ">=12.4.0" "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": { "node_modules/@rtsao/scc": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1197,6 +1281,12 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@swc/helpers": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1488,6 +1578,12 @@
"tslib": "^2.4.0" "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": { "node_modules/@types/better-sqlite3": {
"version": "7.6.13", "version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
@@ -2440,6 +2536,15 @@
"baseline-browser-mapping": "dist/cli.js" "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": { "node_modules/better-sqlite3": {
"version": "12.4.1", "version": "12.4.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.1.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.1.tgz",
@@ -2556,6 +2661,46 @@
"ieee754": "^1.1.13" "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": { "node_modules/call-bind": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "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" "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": { "node_modules/chownr": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC" "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": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2692,6 +2861,21 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -2831,6 +3015,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/define-data-property": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "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" "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": { "node_modules/detect-libc": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2889,6 +3094,18 @@
"node": ">=0.10.0" "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": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2904,6 +3121,16 @@
"node": ">= 0.4" "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": { "node_modules/electron-to-chromium": {
"version": "1.5.243", "version": "1.5.243",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.243.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.243.tgz",
@@ -2918,6 +3145,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/end-of-stream": {
"version": "1.4.5", "version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -3574,6 +3810,34 @@
"node": ">=6" "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": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "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" "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": { "node_modules/github-from-package": {
"version": "0.0.0", "version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@@ -4580,7 +4861,6 @@
"version": "2.6.1", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
@@ -5262,6 +5542,12 @@
"node": ">=10" "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": { "node_modules/node-releases": {
"version": "2.0.26", "version": "2.0.26",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
@@ -5269,6 +5555,25 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5392,6 +5697,12 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -5509,6 +5820,18 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5528,6 +5851,17 @@
"url": "https://github.com/sponsors/jonschlinkert" "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": { "node_modules/possible-typed-array-names": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "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": ">= 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": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5635,6 +5994,22 @@
"node": ">=6" "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": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5680,6 +6055,16 @@
"node": ">=0.10.0" "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": { "node_modules/react": {
"version": "19.2.0", "version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
@@ -5722,6 +6107,19 @@
"node": ">= 6" "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": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -6455,6 +6853,15 @@
"node": ">=6" "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": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -6668,7 +7075,7 @@
"version": "5.9.3", "version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",

View File

@@ -6,12 +6,21 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "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": { "dependencies": {
"@prisma/client": "^6.19.0",
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.4.1", "better-sqlite3": "^12.4.1",
"dotenv": "^17.2.3",
"next": "16.0.1", "next": "16.0.1",
"prisma": "^6.19.0",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0" "react-dom": "19.2.0"
}, },

24
prisma.config.ts Normal file
View 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

Binary file not shown.

91
prisma/schema.prisma Normal file
View 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")
}