You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
6.1 KiB

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')
const mysql = require('mysql2/promise')
const bcrypt = require('bcryptjs')
const app = express()
app.use(cors({ origin: '*', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE' }))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
const ok = data => ({ success: true, data })
const now = () => new Date().toISOString()
let pool = null
const initDb = async () => {
const url = process.env.DATABASE_URL
if (!url) return
const u = new URL(url)
const config = {
host: u.hostname,
port: Number(u.port || 3306),
user: decodeURIComponent(u.username || ''),
password: decodeURIComponent(u.password || ''),
database: (u.pathname || '').replace(/^\//, '') || process.env.DB_NAME || 'smtp',
waitForConnections: true,
connectionLimit: Number(process.env.DB_CONN_LIMIT || 10),
queueLimit: 0
}
pool = mysql.createPool(config)
}
const dbPing = async () => {
if (!pool) return { connected: false, message: 'DATABASE_URL not set' }
const [rows] = await pool.query('SELECT 1 AS ok')
return { connected: true, rows }
}
app.get('/api/db/ping', async (req, res) => {
try {
const r = await dbPing()
res.json(ok(r))
} catch (e) {
res.status(500).json({ success: false, message: String(e) })
}
})
app.get('/api/settings.php', (req, res) => {
res.json(ok({
smtp: { port: 25, host: '0.0.0.0', max_connections: 100 },
pop3: { port: 110, host: '0.0.0.0', max_connections: 100 },
server: { domain: 'test.com', max_email_size: 10485760, max_mailbox_size: 104857600 },
log: { path: '../logs/', level: 'info' }
}))
})
app.post('/api/settings.php', (req, res) => res.json(ok({ saved: true, at: now(), payload: req.body || {} })))
app.get('/api/users.php', (req, res) => {
const users = [
{ username: 'admin', email: 'admin@test.com', is_admin: true, is_enabled: true, created_at: now(), updated_at: now() },
{ username: 'user', email: 'user@test.com', is_admin: false, is_enabled: true, created_at: now(), updated_at: now() }
]
const page = Number(req.query.page || 1), perPage = Number(req.query.perPage || 10)
res.json(ok({ users, total: users.length, page, perPage, totalPages: 1 }))
})
app.post('/api/users.php', async (req, res) => {
const { login, username, password } = req.body || {}
if (login) {
try {
if (!pool) return res.status(500).json({ success: false, message: 'Database not initialized' })
const [rows] = await pool.query('SELECT * FROM `user` WHERE username = ? AND is_deleted = 0 AND is_enabled = 1 LIMIT 1', [username])
const user = rows && rows[0]
if (!user) return res.status(401).json({ success: false, message: 'Invalid credentials' })
const stored = user.password || ''
const okPass = stored.startsWith('$2') ? bcrypt.compareSync(password, stored) : (stored === password)
if (!okPass) return res.status(401).json({ success: false, message: 'Invalid credentials' })
const token = Buffer.from(`${username}:${now()}`).toString('base64')
return res.json(ok({ token, username, login_at: now() }))
} catch (e) {
return res.status(500).json({ success: false, message: String(e) })
}
}
res.json(ok({ created: true, user: req.body || {} }))
})
app.delete('/api/users.php', (req, res) => res.json(ok({ deleted: true, username: req.query.username || '' })))
app.get('/api/emails.php', (req, res) => {
const { id, username, folder = 'inbox', page = 1, perPage = 10 } = req.query
if (id) return res.json(ok({ email: null }))
const emails = [
{ id: '1234567890', from: 'sender@example.com', to: 'admin@test.com', subject: '测试邮件', date: now(), folder, is_read: 0, is_deleted: 0, data: '这是邮件内容', created_at: now() },
]
res.json(ok({ emails, total: emails.length, page: Number(page), perPage: Number(perPage), totalPages: 1 }))
})
app.post('/api/emails.php', (req, res) => res.json(ok({ sent: !req.body?.isDraft, draft: !!req.body?.isDraft, payload: req.body || {} })))
app.put('/api/emails.php', (req, res) => res.json(ok({ updated: true, payload: req.body || {} })))
app.delete('/api/emails.php', (req, res) => res.json(ok({ deleted: true, id: req.query.id || '', username: req.query.username || '' })))
app.get('/api/logs.php', (req, res) => {
const logs = [{ id: 1, type: 'info', message: 'Server started', ip: '127.0.0.1', user_id: 'system', created_at: now() }]
res.json(ok({ logs, total: logs.length, page: Number(req.query.page || 1), perPage: Number(req.query.perPage || 10), totalPages: 1 }))
})
app.delete('/api/logs.php', (req, res) => res.json(ok({ deleted: req.query.all ? 'all' : req.query.id || '' })))
app.get('/api/contacts.php', (req, res) => {
const { id } = req.query
const item = { id: 1, name: '张三', email: 'zhangsan@example.com', phone: '13800138000', company: '示例公司', department: '技术部', position: '工程师', create_time: now(), update_time: now() }
if (id) return res.json(ok({ contact: item }))
res.json(ok({ contacts: [item], total: 1, page: Number(req.query.page || 1), perPage: Number(req.query.perPage || 20), totalPages: 1 }))
})
app.post('/api/contacts.php', (req, res) => res.json(ok({ created: true, contact: req.body || {} })))
app.put('/api/contacts.php', (req, res) => res.json(ok({ updated: true, contact: req.body || {} })))
app.delete('/api/contacts.php', (req, res) => res.json(ok({ deleted: true, id: Number(req.query.id || 0) })))
app.get('/api/stats.php', (req, res) => {
res.json(ok({ typeStats: { inbox: 0, sent: 0, draft: 0, trash: 0, total: 0 }, readStats: { read: 0, unread: 0 } }))
})
app.get('/api/mailbox.php', (req, res) => {
const { action } = req.query
if (action === 'quota') return res.json(ok({ quota: 1073741824, used: 0, remaining: 1073741824, percentage: 0 }))
res.json(ok({ email: 'admin@test.com', stats: { inbox: 0, sent: 0, draft: 0, trash: 0, unread: 0 }, customFolders: [] }))
})
const port = process.env.PORT || 8000
initDb()
.catch(e => console.error('DB init error', e))
.finally(() => {
app.listen(port, () => {
console.log(`API server on http://localhost:${port}/`)
})
})