first commit
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
# Prisma client 생성 및 TypeScript 빌드
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install --only=production
|
||||
|
||||
COPY --from=builder /usr/src/app/dist ./dist
|
||||
COPY --from=builder /usr/src/app/node_modules/.prisma ./node_modules/.prisma
|
||||
COPY --from=builder /usr/src/app/node_modules/@prisma/client ./node_modules/@prisma/client
|
||||
COPY --from=builder /usr/src/app/prisma ./prisma
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
# Prisma 마이그레이션 실행 후 백엔드 서버 실행
|
||||
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "kindergarten-diary-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Kindergarten Diary AI Generator Backend",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/generative-ai": "^0.1.1",
|
||||
"@prisma/client": "^5.10.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.3",
|
||||
"mysql2": "^3.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.11.24",
|
||||
"nodemon": "^3.1.0",
|
||||
"prisma": "^5.10.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
model Diary {
|
||||
id Int @id @default(autoincrement())
|
||||
childName String
|
||||
originalContent String @db.Text
|
||||
generatedContent String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { GoogleGenAI } from '@google/generative-ai';
|
||||
|
||||
// 환경 변수 설정
|
||||
dotenv.config();
|
||||
dotenv.config({ path: '.env.production' });
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 4000;
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 헬스체크 API
|
||||
app.get('/api/health', (req: Request, res: Response) => {
|
||||
res.status(200).json({ status: 'ok', message: '백엔드 서버가 정상 구동 중입니다.' });
|
||||
});
|
||||
|
||||
// AI 일지 작성 API
|
||||
app.post('/api/diary/generate', async (req: Request, res: Response) => {
|
||||
const { content, keywords, childName } = req.body;
|
||||
|
||||
// AI 설정 값 읽기 (env.production에 설정 예정)
|
||||
const apiKey = process.env.AI_API_KEY;
|
||||
const modelName = process.env.AI_MODEL || 'gemini-1.5-flash';
|
||||
|
||||
if (!apiKey) {
|
||||
return res.status(500).json({
|
||||
error: 'AI_API_KEY 환경 변수가 설정되지 않았습니다. .env.production 파일을 확인해 주세요.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!content && (!keywords || keywords.length === 0)) {
|
||||
return res.status(400).json({ error: '일지 내용 또는 키워드를 입력해 주세요.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const ai = new GoogleGenAI({ apiKey });
|
||||
const model = ai.getGenerativeModel({ model: modelName });
|
||||
|
||||
const prompt = `
|
||||
당신은 유치원 교사입니다. 교사가 작성한 간단한 메모나 키워드를 바탕으로 학부모님께 보낼 정성스럽고 자연스러운 알림장(유치원 일지)을 작성해 주세요.
|
||||
|
||||
[아이 이름]
|
||||
${childName || '아이'}
|
||||
|
||||
[교사 작성 내용]
|
||||
${content || ''}
|
||||
|
||||
[키워드]
|
||||
${keywords ? keywords.join(', ') : ''}
|
||||
|
||||
[작성 지침]
|
||||
1. 정중하고 따뜻한 어조(해요체)로 작성해 주세요.
|
||||
2. 아이의 활동을 구체적이고 긍정적으로 묘사해 주세요.
|
||||
3. 너무 길지 않고 가독성 좋게 단락을 나누어 작성해 주세요.
|
||||
4. 학부모님께 드리는 인사와 마무리 멘트를 포함해 주세요.
|
||||
`;
|
||||
|
||||
const result = await model.generateContent(prompt);
|
||||
const responseText = result.response.text();
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
originalContent: content,
|
||||
generatedContent: responseText,
|
||||
modelUsed: modelName
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('AI 생성 에러:', error);
|
||||
return res.status(500).json({
|
||||
error: 'AI 일지 작성 중 에러가 발생했습니다.',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 일지 저장 API (DB 연동)
|
||||
app.post('/api/diary/save', async (req: Request, res: Response) => {
|
||||
const { childName, originalContent, generatedContent } = req.body;
|
||||
|
||||
try {
|
||||
const newDiary = await prisma.diary.create({
|
||||
data: {
|
||||
childName: childName || '미지정',
|
||||
originalContent: originalContent || '',
|
||||
generatedContent: generatedContent || '',
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '일지가 데이터베이스에 정상적으로 저장되었습니다.',
|
||||
data: newDiary
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('DB 저장 에러:', error);
|
||||
return res.status(500).json({
|
||||
error: '데이터베이스 저장 중 에러가 발생했습니다.',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 전체 일지 조회 API
|
||||
app.get('/api/diary/list', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const diaries = await prisma.diary.findMany({
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
});
|
||||
return res.status(200).json({ success: true, data: diaries });
|
||||
} catch (error: any) {
|
||||
console.error('DB 조회 에러:', error);
|
||||
return res.status(500).json({
|
||||
error: '데이터베이스 조회 중 에러가 발생했습니다.',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`서버가 http://localhost:${port} 에서 구동 중입니다.`);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2022"],
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"*": ["node_modules/*", "src/*"]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user