Files
memind/wiki-server.mjs
T
John 2e14873f2d Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.
Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 15:04:43 -07:00

124 lines
4.4 KiB
JavaScript

import express from 'express';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createWikiAuth } from './wiki-auth.mjs';
import { PUBLISH_ROOT_DIR } from './user-publish.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PORT = Number(process.env.WIKI_PORT ?? 3000);
const app = express();
app.set('trust proxy', 1);
const auth = createWikiAuth(path.join(__dirname, PUBLISH_ROOT_DIR, 'wiki-db'));
const jsonBody = express.json({ limit: '1mb' });
function parseCookies(header = '') {
return Object.fromEntries(
header.split(';').map(p => p.trim()).filter(Boolean).map(p => {
const idx = p.indexOf('=');
return idx < 0 ? [p, ''] : [decodeURIComponent(p.slice(0, idx)), decodeURIComponent(p.slice(idx + 1))];
}),
);
}
function requireAuth(req, res, next) {
const cookies = parseCookies(req.get('cookie'));
const token = cookies[auth.COOKIE_NAME];
const session = auth.verify(token);
if (!session) {
return res.status(401).json({ message: '未登录' });
}
req.user = session;
next();
}
// ============ Auth Routes ============
app.post('/api/auth/register', jsonBody, (req, res) => {
const { username, password, displayName } = req.body || {};
if (!username || !password) {
return res.status(400).json({ message: '用户名和密码不能为空' });
}
if (username.length < 2 || username.length > 20) {
return res.status(400).json({ message: '用户名长度2-20个字符' });
}
if (password.length < 4) {
return res.status(400).json({ message: '密码至少4个字符' });
}
const result = auth.register(username, password, displayName);
if (!result.ok) {
return res.status(409).json({ message: result.message });
}
res.json({ ok: true, user: result.user });
});
app.post('/api/auth/login', jsonBody, (req, res) => {
const { username, password } = req.body || {};
if (!username || !password) {
return res.status(400).json({ message: '用户名和密码不能为空' });
}
const result = auth.login(username, password);
if (!result.ok) {
return res.status(401).json({ message: result.message });
}
const isSecure = req.secure || req.get('x-forwarded-proto')?.split(',')[0]?.trim() === 'https';
res.set('Set-Cookie', `${auth.COOKIE_NAME}=${encodeURIComponent(result.token)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=604800${isSecure ? '; Secure' : ''}`);
res.json({ ok: true, user: result.user });
});
app.post('/api/auth/logout', (req, res) => {
const cookies = parseCookies(req.get('cookie'));
auth.revoke(cookies[auth.COOKIE_NAME]);
const isSecure = req.secure || req.get('x-forwarded-proto')?.split(',')[0]?.trim() === 'https';
res.set('Set-Cookie', `${auth.COOKIE_NAME}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${isSecure ? '; Secure' : ''}`);
res.json({ ok: true });
});
app.get('/api/auth/me', (req, res) => {
const cookies = parseCookies(req.get('cookie'));
const session = auth.verify(cookies[auth.COOKIE_NAME]);
if (!session) return res.json({ authenticated: false });
const user = auth.getUser(session.username);
res.json({ authenticated: true, user });
});
// ============ Page Routes ============
app.get('/api/pages', requireAuth, (req, res) => {
const { q } = req.query;
if (q) {
return res.json(auth.searchPages(req.user.username, q));
}
res.json(auth.listPages(req.user.username));
});
app.get('/api/pages/:slug', requireAuth, (req, res) => {
const page = auth.getPage(req.user.username, req.params.slug);
if (!page) return res.status(404).json({ message: '页面不存在' });
res.json(page);
});
app.post('/api/pages/:slug', requireAuth, jsonBody, (req, res) => {
const { title, content, tags } = req.body || {};
const slug = req.params.slug;
const page = auth.savePage(req.user.username, slug, title, content, tags);
res.json(page);
});
app.delete('/api/pages/:slug', requireAuth, (req, res) => {
auth.deletePage(req.user.username, req.params.slug);
res.json({ ok: true });
});
// ============ Static Files ============
app.use('/wiki', express.static(path.join(__dirname, PUBLISH_ROOT_DIR, 'wiki')));
app.get('/wiki/*', (req, res) => {
res.sendFile(path.join(__dirname, PUBLISH_ROOT_DIR, 'wiki', 'index.html'));
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`📚 Wiki系统运行中 @ http://0.0.0.0:${PORT}/wiki`);
console.log(` 数据目录: ${path.join(__dirname, PUBLISH_ROOT_DIR, 'wiki-db')}`);
});