diff --git a/.gitignore b/.gitignore index f9eba1f..e202f50 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -custom_order.class \ No newline at end of file +custom_order.class +node_modules +uploads +npm-debug.log* diff --git a/README.md b/README.md index 0a94ab4..db453ca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ # Introduction to Java This your copy of the code for the java-course. The course itself is located in the issues of this repository. + +## Love Story Backend (Node + TypeScript) + +A lightweight Express API that powers the couple-themed front-end shown in the mockups. It includes photo uploads, chat history with playful bot replies, preset hero/banner text, love-letter storage, and simple game card metadata. + +### Getting started +1. Install dependencies: + ```bash + npm install + ``` +2. Start the development server (watch mode with TypeScript): + ```bash + npm run dev + ``` +3. Or build and run the compiled server: + ```bash + npm run build + npm start + ``` + +The API listens on `http://localhost:8080` by default and serves uploaded photos from `/uploads`. + +### Key endpoints +- `GET /api/health` – health probe. +- `GET /api/hero` – banner names, subtitle, and badge text. +- `GET /api/stats` – summary counts for love days, photos, chats, and letter date. +- `GET /api/game-cards` – metadata for the three game cards. +- `GET /api/memories` – list of uploaded photos (in memory during runtime). +- `POST /api/memories` – upload a photo (`photo` field) with optional `caption`. +- `GET /api/chat/history` – conversation history. +- `POST /api/chat/message` – send a message and receive a contextual bot reply. +- `GET /api/love-letter` / `PUT /api/love-letter` – retrieve or update the shared letter. + +Uploads are stored on disk under `uploads/` (created automatically) and served as static files. The service keeps data in memory for simplicity; restart the server to reset state. diff --git a/package.json b/package.json new file mode 100644 index 0000000..2ffbce9 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "java-course-backend", + "version": "1.0.0", + "description": "Backend service to support the TypeScript front-end experience", + "main": "dist/server.js", + "scripts": { + "build": "tsc", + "start": "node dist/server.js", + "dev": "ts-node src/server.ts" + }, + "dependencies": { + "cors": "^2.8.5", + "express": "^4.18.2", + "multer": "^1.4.5-lts.1" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/multer": "^1.4.7", + "@types/node": "^20.10.5", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } +} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..b745fc5 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,214 @@ +import cors from 'cors'; +import express, { Request, Response } from 'express'; +import fs from 'fs'; +import multer from 'multer'; +import path from 'path'; + +// Simple in-memory stores that mimic persistent data. +const memories: Memory[] = []; +const chatHistory: ChatMessage[] = []; +const loveLetter: LoveLetter = { + author: '宗睿', + recipient: '代琪', + title: '致 代琪', + content: + '亲爱的:\n\n感谢你成为我生活中的“专属温暖”。无论平静的午后,还是喧嚣的夜晚,我都想第一时间把喜悦和烦恼告诉你。\n\n这份网站想记录我们“回忆、美食、游戏、聊天”的每一个瞬间,它不只是一份作业,更是我们的数字小窝。希望未来有更多的故事被放进这里。\n\n愿你开心、健康,也愿我始终有勇气守护你。\n\n爱你的,宗睿', + date: new Date().toISOString() +}; + +interface Memory { + id: string; + fileName: string; + caption?: string; + createdAt: string; +} + +interface ChatMessage { + id: string; + sender: 'user' | 'bot'; + text: string; + createdAt: string; +} + +interface LoveLetter { + author: string; + recipient: string; + title: string; + content: string; + date: string; +} + +interface GameCard { + id: string; + title: string; + description: string; + action: string; +} + +const app = express(); +const port = process.env.PORT || 8080; +const uploadDir = path.join(__dirname, '..', 'uploads'); + +if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); +} + +const storage = multer.diskStorage({ + destination: (_req, _file, cb) => cb(null, uploadDir), + filename: (_req, file, cb) => { + const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e6)}`; + cb(null, `${uniqueSuffix}${path.extname(file.originalname)}`); + } +}); + +const upload = multer({ storage }); + +app.use(cors()); +app.use(express.json()); +app.use('/uploads', express.static(uploadDir)); + +app.get('/api/health', (_req: Request, res: Response) => { + res.json({ status: 'ok' }); +}); + +app.get('/api/game-cards', (_req: Request, res: Response) => { + const cards: GameCard[] = [ + { + id: 'hearts', + title: '爱情默契卡', + description: '10道甜蜜小问答,测试彼此的默契值并生成专属徽章。', + action: '开始' + }, + { + id: 'puzzle', + title: '爱的拼图游戏', + description: '上传一张照片,自动生成 3x3 拼图,限时完成挑战。', + action: '组拼' + }, + { + id: 'memory', + title: '甜蜜照片墙', + description: '把我们的照片、合影、旅行纪念都上传成相册墙。', + action: '看回忆' + } + ]; + + res.json(cards); +}); + +app.get('/api/memories', (_req: Request, res: Response) => { + res.json(memories); +}); + +app.post('/api/memories', upload.single('photo'), (req: Request, res: Response) => { + if (!req.file) { + return res.status(400).json({ error: '缺少图片文件' }); + } + + const memory: Memory = { + id: `${Date.now()}`, + fileName: `/uploads/${req.file.filename}`, + caption: req.body.caption, + createdAt: new Date().toISOString() + }; + + memories.unshift(memory); + res.status(201).json(memory); +}); + +app.get('/api/chat/history', (_req: Request, res: Response) => { + res.json(chatHistory); +}); + +app.post('/api/chat/message', (req: Request, res: Response) => { + const text: string | undefined = req.body?.text; + + if (!text || !text.trim()) { + return res.status(400).json({ error: '消息内容不能为空' }); + } + + const userMessage: ChatMessage = { + id: `${Date.now()}-user`, + sender: 'user', + text: text.trim(), + createdAt: new Date().toISOString() + }; + + chatHistory.push(userMessage); + + const botMessage: ChatMessage = { + id: `${Date.now()}-bot`, + sender: 'bot', + text: buildBotReply(text), + createdAt: new Date().toISOString() + }; + + chatHistory.push(botMessage); + + res.status(201).json({ userMessage, botMessage }); +}); + +app.get('/api/love-letter', (_req: Request, res: Response) => { + res.json(loveLetter); +}); + +app.put('/api/love-letter', (req: Request, res: Response) => { + const { author, recipient, title, content } = req.body; + + if (!author || !recipient || !title || !content) { + return res.status(400).json({ error: '请填写完整的信件信息' }); + } + + loveLetter.author = author; + loveLetter.recipient = recipient; + loveLetter.title = title; + loveLetter.content = content; + loveLetter.date = new Date().toISOString(); + + res.json(loveLetter); +}); + +app.get('/api/hero', (_req: Request, res: Response) => { + res.json({ + primaryName: '樊宗睿', + secondaryName: '代琪', + subtitle: '成为你的可爱,是我学会坚强的魔法', + badge: '恋爱日记' + }); +}); + +app.get('/api/stats', (_req: Request, res: Response) => { + res.json({ + loveDays: 288, + photosCount: memories.length, + chatCount: chatHistory.length, + letterDate: loveLetter.date + }); +}); + +app.use((err: Error, _req: Request, res: Response, _next: () => void) => { + console.error(err); + res.status(500).json({ error: '服务器开小差了,请稍后再试' }); +}); + +app.listen(port, () => { + console.log(`Love story backend is running on http://localhost:${port}`); +}); + +function buildBotReply(message: string): string { + const normalized = message.toLowerCase(); + + if (normalized.includes('开心') || normalized.includes('快乐')) { + return '听到你开心我也很开心,我们去吃好吃的庆祝一下吧!'; + } + + if (normalized.includes('难过') || normalized.includes('累')) { + return '抱抱你,累了就歇一会儿,我来给你煮奶茶,一起慢慢聊~'; + } + + if (normalized.includes('晚安')) { + return '晚安好梦,做个甜甜的梦,梦到我!'; + } + + return '收到!我一直在这儿陪你,告诉我更多细节,我们一起想办法。'; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b949c0e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "rootDir": "src", + "outDir": "dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +}