-
Notifications
You must be signed in to change notification settings - Fork 0
Add TypeScript Express backend with love story APIs #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Fstarnb
wants to merge
1
commit into
master
Choose a base branch
from
codex/create-backend-for-dynamic-website
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,4 @@ | ||
| custom_order.class | ||
| custom_order.class | ||
| node_modules | ||
| uploads | ||
| npm-debug.log* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 '收到!我一直在这儿陪你,告诉我更多细节,我们一起想办法。'; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2020", | ||
| "module": "commonjs", | ||
| "rootDir": "src", | ||
| "outDir": "dist", | ||
| "esModuleInterop": true, | ||
| "forceConsistentCasingInFileNames": true, | ||
| "strict": true, | ||
| "skipLibCheck": true | ||
| }, | ||
| "include": ["src/**/*"] | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeScript imports
corsbut package.json does not declare@types/cors, so runningnpm run build(tsc) ornpm run dev(ts-node) will error with TS7016 that it cannot find declarations forcors, blocking both the compiled and dev server entrypoints; include the type package or adjust the import to restore builds.Useful? React with 👍 / 👎.