first commit

This commit is contained in:
2026-06-22 21:36:51 +09:00
commit fb25027bdc
26 changed files with 1006 additions and 0 deletions
+31
View File
@@ -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"]
+30
View File
@@ -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"
}
}
+17
View File
@@ -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())
}
+130
View File
@@ -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} 에서 구동 중입니다.`);
});
+32
View File
@@ -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"]
}