feat(backend): Express + Socket.IO API with PostgreSQL
- Auth: register/login/me with bcrypt (12 rounds) and JWT bearer tokens - Workspaces: CRUD, role-based members (owner/admin/member/viewer), invite by email - Pages: nested hierarchy, TipTap JSON+HTML content, archive, search, comments - Realtime: Socket.IO rooms per page with JWT handshake, live edits, presence, typing - DB schema with triggers (owner membership, updated_at) and migrate script - Helmet, CORS allow-list, rate limiting, /api/health with DB check - Dockerfile (migrate-on-boot) for compose/production
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import 'dotenv/config';
|
||||
import http from 'node:http';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import morgan from 'morgan';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { pool } from './config/db.js';
|
||||
import authRoutes from './routes/auth.js';
|
||||
import workspaceRoutes from './routes/workspaces.js';
|
||||
import pageRoutes from './routes/pages.js';
|
||||
import userRoutes from './routes/users.js';
|
||||
import { initSocket } from './socket.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = Number(process.env.PORT || 3000);
|
||||
const CORS_ORIGIN = process.env.CORS_ORIGIN || 'http://localhost:5173';
|
||||
|
||||
app.use(helmet());
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.json({ limit: '2mb' }));
|
||||
app.use(
|
||||
cors({
|
||||
origin: CORS_ORIGIN.split(',').map((s) => s.trim()),
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
|
||||
app.use('/api/auth', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }), authRoutes);
|
||||
app.use('/api/workspaces', workspaceRoutes);
|
||||
app.use('/api/pages', pageRoutes);
|
||||
app.use('/api/users', userRoutes);
|
||||
|
||||
app.get('/api/health', async (_req, res) => {
|
||||
try {
|
||||
await pool.query('SELECT 1');
|
||||
res.json({ ok: true, db: 'up' });
|
||||
} catch {
|
||||
res.status(503).json({ ok: false, db: 'down' });
|
||||
}
|
||||
});
|
||||
|
||||
app.use((err, _req, res, _next) => {
|
||||
console.error('[unhandled]', err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
});
|
||||
|
||||
const server = http.createServer(app);
|
||||
const io = initSocket(server, CORS_ORIGIN);
|
||||
app.set('io', io);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`[api] listening on :${PORT} (CORS: ${CORS_ORIGIN})`);
|
||||
});
|
||||
Reference in New Issue
Block a user