db만 구축했는데 안되면 되돌리셈
This commit is contained in:
211
src/app/api/README.md
Normal file
211
src/app/api/README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# API 엔드포인트 문서
|
||||
|
||||
이 문서는 데이터베이스에 데이터를 생성하는 API 엔드포인트를 설명합니다.
|
||||
|
||||
## 📋 API 목록
|
||||
|
||||
### 1. 사용자 API (`/api/users`)
|
||||
|
||||
#### POST - 사용자 생성
|
||||
```bash
|
||||
POST /api/users
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "hashed_password",
|
||||
"name": "홍길동",
|
||||
"phone": "010-1234-5678",
|
||||
"gender": "M",
|
||||
"birthdate": "1990-01-01",
|
||||
"role": "LEARNER", // 또는 "ADMIN"
|
||||
"status": "ACTIVE" // 또는 "INACTIVE"
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"message": "사용자가 성공적으로 생성되었습니다.",
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"name": "홍길동",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET - 사용자 목록 조회
|
||||
```bash
|
||||
GET /api/users?role=LEARNER&status=ACTIVE&page=1&limit=10
|
||||
```
|
||||
|
||||
**쿼리 파라미터:**
|
||||
- `role`: 필터링할 역할 (LEARNER, ADMIN)
|
||||
- `status`: 필터링할 상태 (ACTIVE, INACTIVE)
|
||||
- `page`: 페이지 번호 (기본값: 1)
|
||||
- `limit`: 페이지당 항목 수 (기본값: 10)
|
||||
|
||||
---
|
||||
|
||||
### 2. 교육과정 API (`/api/courses`)
|
||||
|
||||
#### POST - 교육과정 생성
|
||||
```bash
|
||||
POST /api/courses
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"courseName": "웹 개발 기초",
|
||||
"instructorId": "instructor_uuid",
|
||||
"createdById": "admin_uuid" // 선택사항, 기본값: instructorId
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"message": "교육과정이 성공적으로 생성되었습니다.",
|
||||
"course": {
|
||||
"id": "uuid",
|
||||
"courseName": "웹 개발 기초",
|
||||
"instructor": { ... },
|
||||
"createdBy": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET - 교육과정 목록 조회
|
||||
```bash
|
||||
GET /api/courses?instructorId=uuid&page=1&limit=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 강좌 API (`/api/lessons`)
|
||||
|
||||
#### POST - 강좌 생성
|
||||
```bash
|
||||
POST /api/lessons
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"courseId": "course_uuid",
|
||||
"lessonName": "HTML 기초",
|
||||
"learningGoal": "HTML의 기본 문법을 이해하고 활용할 수 있다.",
|
||||
"createdById": "admin_uuid" // 선택사항
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"message": "강좌가 성공적으로 생성되었습니다.",
|
||||
"lesson": {
|
||||
"id": "uuid",
|
||||
"lessonName": "HTML 기초",
|
||||
"course": { ... },
|
||||
"createdBy": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET - 강좌 목록 조회
|
||||
```bash
|
||||
GET /api/lessons?courseId=uuid&page=1&limit=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 공지사항 API (`/api/notices`)
|
||||
|
||||
#### POST - 공지사항 생성
|
||||
```bash
|
||||
POST /api/notices
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "공지사항 제목",
|
||||
"content": "공지사항 내용",
|
||||
"writerId": "admin_uuid",
|
||||
"hasAttachment": false // 선택사항
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"message": "공지사항이 성공적으로 생성되었습니다.",
|
||||
"notice": {
|
||||
"id": "uuid",
|
||||
"title": "공지사항 제목",
|
||||
"content": "공지사항 내용",
|
||||
"writer": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET - 공지사항 목록 조회
|
||||
```bash
|
||||
GET /api/notices?writerId=uuid&page=1&limit=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 사용 예시
|
||||
|
||||
### JavaScript/TypeScript (fetch)
|
||||
|
||||
```typescript
|
||||
// 사용자 생성
|
||||
const response = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: 'user@example.com',
|
||||
password: 'hashed_password',
|
||||
name: '홍길동',
|
||||
role: 'LEARNER',
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
```
|
||||
|
||||
### cURL
|
||||
|
||||
```bash
|
||||
# 사용자 생성
|
||||
curl -X POST http://localhost:3000/api/users \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "user@example.com",
|
||||
"password": "hashed_password",
|
||||
"name": "홍길동",
|
||||
"role": "LEARNER"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 주의사항
|
||||
|
||||
1. **비밀번호 해시화**: 실제 프로덕션에서는 비밀번호를 해시화하여 저장해야 합니다.
|
||||
2. **인증/인가**: 현재 API는 인증이 없습니다. 프로덕션에서는 JWT나 세션 기반 인증을 추가해야 합니다.
|
||||
3. **에러 처리**: 모든 API는 적절한 에러 응답을 반환합니다.
|
||||
4. **데이터 검증**: 필수 필드 검증이 포함되어 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 📝 다음 단계
|
||||
|
||||
- [ ] 인증 미들웨어 추가
|
||||
- [ ] 비밀번호 해시화 로직 추가
|
||||
- [ ] 파일 업로드 API 추가 (공지사항 첨부파일 등)
|
||||
- [ ] 수정/삭제 API 추가
|
||||
- [ ] 상세 조회 API 추가
|
||||
|
||||
126
src/app/api/courses/route.ts
Normal file
126
src/app/api/courses/route.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
// 교육과정 생성
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { courseName, instructorId, createdById } = body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!courseName || !instructorId) {
|
||||
return NextResponse.json(
|
||||
{ error: '교육과정명과 강사 ID는 필수입니다.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 강사 존재 확인
|
||||
const instructor = await prisma.user.findUnique({
|
||||
where: { id: instructorId },
|
||||
});
|
||||
|
||||
if (!instructor) {
|
||||
return NextResponse.json(
|
||||
{ error: '강사를 찾을 수 없습니다.' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// 교육과정 생성
|
||||
const course = await prisma.course.create({
|
||||
data: {
|
||||
courseName,
|
||||
instructorId,
|
||||
createdById: createdById || instructorId,
|
||||
},
|
||||
include: {
|
||||
instructor: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
createdBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: '교육과정이 성공적으로 생성되었습니다.', course },
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('교육과정 생성 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '교육과정 생성 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 교육과정 목록 조회
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const instructorId = searchParams.get('instructorId');
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const limit = parseInt(searchParams.get('limit') || '10');
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = {};
|
||||
if (instructorId) where.instructorId = instructorId;
|
||||
|
||||
const [courses, total] = await Promise.all([
|
||||
prisma.course.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
instructor: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
createdBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
lessons: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.course.count({ where }),
|
||||
]);
|
||||
|
||||
return NextResponse.json({
|
||||
courses,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('교육과정 조회 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '교육과정 조회 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
272
src/app/api/examples.ts
Normal file
272
src/app/api/examples.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* API 사용 예제 모음
|
||||
*
|
||||
* 이 파일은 API를 사용하는 다양한 예제를 제공합니다.
|
||||
* 실제 프로젝트에서 참고하여 사용하세요.
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// 1. 사용자 API 예제
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 사용자 생성 예제
|
||||
*/
|
||||
export async function createUserExample() {
|
||||
const response = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: 'newuser@example.com',
|
||||
password: 'hashed_password_here', // 실제로는 bcrypt로 해시화
|
||||
name: '홍길동',
|
||||
phone: '010-1234-5678',
|
||||
gender: 'M',
|
||||
birthdate: '1990-01-01',
|
||||
role: 'LEARNER',
|
||||
status: 'ACTIVE',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 목록 조회 예제 (필터링)
|
||||
*/
|
||||
export async function getUsersExample() {
|
||||
const params = new URLSearchParams({
|
||||
role: 'LEARNER',
|
||||
status: 'ACTIVE',
|
||||
page: '1',
|
||||
limit: '10',
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/users?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 2. 교육과정 API 예제
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 교육과정 생성 예제
|
||||
*/
|
||||
export async function createCourseExample(instructorId: string) {
|
||||
const response = await fetch('/api/courses', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
courseName: '웹 개발 기초',
|
||||
instructorId: instructorId,
|
||||
createdById: instructorId, // 선택사항
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.course;
|
||||
}
|
||||
|
||||
/**
|
||||
* 교육과정 목록 조회 예제
|
||||
*/
|
||||
export async function getCoursesExample(instructorId?: string) {
|
||||
const params = new URLSearchParams();
|
||||
if (instructorId) {
|
||||
params.append('instructorId', instructorId);
|
||||
}
|
||||
params.append('page', '1');
|
||||
params.append('limit', '10');
|
||||
|
||||
const response = await fetch(`/api/courses?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 3. 강좌 API 예제
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 강좌 생성 예제
|
||||
*/
|
||||
export async function createLessonExample(courseId: string) {
|
||||
const response = await fetch('/api/lessons', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
courseId: courseId,
|
||||
lessonName: 'HTML 기초',
|
||||
learningGoal: 'HTML의 기본 문법을 이해하고 활용할 수 있다.',
|
||||
createdById: undefined, // 선택사항
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.lesson;
|
||||
}
|
||||
|
||||
/**
|
||||
* 강좌 목록 조회 예제
|
||||
*/
|
||||
export async function getLessonsExample(courseId?: string) {
|
||||
const params = new URLSearchParams();
|
||||
if (courseId) {
|
||||
params.append('courseId', courseId);
|
||||
}
|
||||
params.append('page', '1');
|
||||
params.append('limit', '10');
|
||||
|
||||
const response = await fetch(`/api/lessons?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 4. 공지사항 API 예제
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 공지사항 생성 예제
|
||||
*/
|
||||
export async function createNoticeExample(writerId: string) {
|
||||
const response = await fetch('/api/notices', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: '공지사항 제목',
|
||||
content: '공지사항 내용입니다.\n여러 줄로 작성할 수 있습니다.',
|
||||
writerId: writerId,
|
||||
hasAttachment: false,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.notice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 공지사항 목록 조회 예제
|
||||
*/
|
||||
export async function getNoticesExample(writerId?: string) {
|
||||
const params = new URLSearchParams();
|
||||
if (writerId) {
|
||||
params.append('writerId', writerId);
|
||||
}
|
||||
params.append('page', '1');
|
||||
params.append('limit', '10');
|
||||
|
||||
const response = await fetch(`/api/notices?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 5. 통합 예제 - 전체 워크플로우
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 전체 워크플로우 예제
|
||||
* 1. 관리자 사용자 생성
|
||||
* 2. 교육과정 생성
|
||||
* 3. 강좌 생성
|
||||
* 4. 공지사항 생성
|
||||
*/
|
||||
export async function completeWorkflowExample() {
|
||||
try {
|
||||
// 1. 관리자 사용자 생성
|
||||
const adminUser = await createUserExample();
|
||||
console.log('관리자 생성:', adminUser);
|
||||
|
||||
// 2. 교육과정 생성
|
||||
const course = await createCourseExample(adminUser.id);
|
||||
console.log('교육과정 생성:', course);
|
||||
|
||||
// 3. 강좌 생성
|
||||
const lesson = await createLessonExample(course.id);
|
||||
console.log('강좌 생성:', lesson);
|
||||
|
||||
// 4. 공지사항 생성
|
||||
const notice = await createNoticeExample(adminUser.id);
|
||||
console.log('공지사항 생성:', notice);
|
||||
|
||||
return {
|
||||
admin: adminUser,
|
||||
course,
|
||||
lesson,
|
||||
notice,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('워크플로우 실행 중 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 6. 에러 처리 예제
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 에러 처리를 포함한 안전한 API 호출 예제
|
||||
*/
|
||||
export async function safeApiCall<T>(
|
||||
apiCall: () => Promise<Response>
|
||||
): Promise<{ data?: T; error?: string }> {
|
||||
try {
|
||||
const response = await apiCall();
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
return { error: errorData.error || '알 수 없는 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return { data: data as T };
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 사용 예시:
|
||||
// const result = await safeApiCall(() =>
|
||||
// fetch('/api/users', { method: 'POST', ... })
|
||||
// );
|
||||
// if (result.error) {
|
||||
// console.error(result.error);
|
||||
// } else {
|
||||
// console.log(result.data);
|
||||
// }
|
||||
|
||||
127
src/app/api/lessons/route.ts
Normal file
127
src/app/api/lessons/route.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
// 강좌 생성
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { courseId, lessonName, learningGoal, createdById } = body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!courseId || !lessonName) {
|
||||
return NextResponse.json(
|
||||
{ error: '교육과정 ID와 강좌명은 필수입니다.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 교육과정 존재 확인
|
||||
const course = await prisma.course.findUnique({
|
||||
where: { id: courseId },
|
||||
});
|
||||
|
||||
if (!course) {
|
||||
return NextResponse.json(
|
||||
{ error: '교육과정을 찾을 수 없습니다.' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// 강좌 생성
|
||||
const lesson = await prisma.lesson.create({
|
||||
data: {
|
||||
courseId,
|
||||
lessonName,
|
||||
learningGoal: learningGoal || null,
|
||||
createdById: createdById || course.createdById,
|
||||
},
|
||||
include: {
|
||||
course: {
|
||||
select: {
|
||||
id: true,
|
||||
courseName: true,
|
||||
},
|
||||
},
|
||||
createdBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: '강좌가 성공적으로 생성되었습니다.', lesson },
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('강좌 생성 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '강좌 생성 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 강좌 목록 조회
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const courseId = searchParams.get('courseId');
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const limit = parseInt(searchParams.get('limit') || '10');
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = {};
|
||||
if (courseId) where.courseId = courseId;
|
||||
|
||||
const [lessons, total] = await Promise.all([
|
||||
prisma.lesson.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
course: {
|
||||
select: {
|
||||
id: true,
|
||||
courseName: true,
|
||||
},
|
||||
},
|
||||
createdBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
videos: true,
|
||||
vrContents: true,
|
||||
questions: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.lesson.count({ where }),
|
||||
]);
|
||||
|
||||
return NextResponse.json({
|
||||
lessons,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('강좌 조회 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '강좌 조회 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
109
src/app/api/notices/route.ts
Normal file
109
src/app/api/notices/route.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
// 공지사항 생성
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { title, content, writerId, hasAttachment } = body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!title || !content || !writerId) {
|
||||
return NextResponse.json(
|
||||
{ error: '제목, 내용, 작성자 ID는 필수입니다.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 작성자 존재 확인
|
||||
const writer = await prisma.user.findUnique({
|
||||
where: { id: writerId },
|
||||
});
|
||||
|
||||
if (!writer) {
|
||||
return NextResponse.json(
|
||||
{ error: '작성자를 찾을 수 없습니다.' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// 공지사항 생성
|
||||
const notice = await prisma.notice.create({
|
||||
data: {
|
||||
title,
|
||||
content,
|
||||
writerId,
|
||||
hasAttachment: hasAttachment || false,
|
||||
},
|
||||
include: {
|
||||
writer: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: '공지사항이 성공적으로 생성되었습니다.', notice },
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('공지사항 생성 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '공지사항 생성 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 공지사항 목록 조회
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const writerId = searchParams.get('writerId');
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const limit = parseInt(searchParams.get('limit') || '10');
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = {};
|
||||
if (writerId) where.writerId = writerId;
|
||||
|
||||
const [notices, total] = await Promise.all([
|
||||
prisma.notice.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { date: 'desc' },
|
||||
include: {
|
||||
writer: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.notice.count({ where }),
|
||||
]);
|
||||
|
||||
return NextResponse.json({
|
||||
notices,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('공지사항 조회 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '공지사항 조회 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
115
src/app/api/users/route.ts
Normal file
115
src/app/api/users/route.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { UserRole, UserStatus } from '@prisma/client';
|
||||
|
||||
// 사용자 생성
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { email, password, name, phone, gender, birthdate, role, status } = body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!email || !password || !name) {
|
||||
return NextResponse.json(
|
||||
{ error: '이메일, 비밀번호, 이름은 필수입니다.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 이메일 중복 확인
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: '이미 존재하는 이메일입니다.' },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
// 사용자 생성
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
password, // 실제로는 해시화된 비밀번호를 저장해야 합니다
|
||||
name,
|
||||
phone: phone || null,
|
||||
gender: gender || null,
|
||||
birthdate: birthdate ? new Date(birthdate) : null,
|
||||
role: role || UserRole.LEARNER,
|
||||
status: status || UserStatus.ACTIVE,
|
||||
},
|
||||
});
|
||||
|
||||
// 비밀번호 제외하고 반환
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: '사용자가 성공적으로 생성되었습니다.', user: userWithoutPassword },
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('사용자 생성 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '사용자 생성 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 사용자 목록 조회
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const role = searchParams.get('role');
|
||||
const status = searchParams.get('status');
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const limit = parseInt(searchParams.get('limit') || '10');
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = {};
|
||||
if (role) where.role = role;
|
||||
if (status) where.status = status;
|
||||
|
||||
const [users, total] = await Promise.all([
|
||||
prisma.user.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
phone: true,
|
||||
gender: true,
|
||||
birthdate: true,
|
||||
role: true,
|
||||
status: true,
|
||||
joinDate: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
}),
|
||||
prisma.user.count({ where }),
|
||||
]);
|
||||
|
||||
return NextResponse.json({
|
||||
users,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('사용자 조회 오류:', error);
|
||||
return NextResponse.json(
|
||||
{ error: '사용자 조회 중 오류가 발생했습니다.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
14
src/lib/prisma.ts
Normal file
14
src/lib/prisma.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user