144 lines
4.4 KiB
JavaScript
144 lines
4.4 KiB
JavaScript
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import crypto from 'node:crypto'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const serverDir = path.dirname(fileURLToPath(import.meta.url))
|
|
const dataDir = process.env.MAIL_DATA_DIR || path.join(serverDir, 'data')
|
|
const messagesFile = path.join(dataDir, 'messages.json')
|
|
|
|
let writeQueue = Promise.resolve()
|
|
|
|
async function ensureStore() {
|
|
await fs.mkdir(dataDir, { recursive: true })
|
|
try {
|
|
await fs.access(messagesFile)
|
|
} catch {
|
|
await fs.writeFile(messagesFile, '[]', 'utf8')
|
|
}
|
|
}
|
|
|
|
async function readMessages() {
|
|
await ensureStore()
|
|
const raw = await fs.readFile(messagesFile, 'utf8')
|
|
try {
|
|
return JSON.parse(raw)
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
function queueWrite(messages) {
|
|
writeQueue = writeQueue.then(async () => {
|
|
const tmp = `${messagesFile}.${process.pid}.tmp`
|
|
await fs.writeFile(tmp, JSON.stringify(messages, null, 2), 'utf8')
|
|
await fs.rename(tmp, messagesFile)
|
|
})
|
|
return writeQueue
|
|
}
|
|
|
|
export async function initStore() {
|
|
await ensureStore()
|
|
if (process.env.SEED_DEMO === 'true') {
|
|
const messages = await readMessages()
|
|
if (!messages.length) {
|
|
const now = Date.now()
|
|
await queueWrite([
|
|
makeMessage({
|
|
folder: 'inbox',
|
|
from: 'Cloudflare <[email protected]>',
|
|
to: process.env.MAIL_USER || '[email protected]',
|
|
subject: '你的 wpyw.site 邮件服务已准备就绪',
|
|
text: '这是本地演示邮件。正式使用时,来自公网的 SMTP 邮件会自动进入这里。',
|
|
date: new Date(now - 1000 * 60 * 12).toISOString(),
|
|
unread: true,
|
|
}),
|
|
makeMessage({
|
|
folder: 'inbox',
|
|
from: '系统管理员 <[email protected]>',
|
|
to: process.env.MAIL_USER || '[email protected]',
|
|
subject: '欢迎使用 wpyw.mail',
|
|
text: '你可以从左侧开始管理收件箱,或点击右上角写信。',
|
|
date: new Date(now - 1000 * 60 * 60 * 4).toISOString(),
|
|
unread: false,
|
|
}),
|
|
makeMessage({
|
|
folder: 'sent',
|
|
from: process.env.MAIL_USER || '[email protected]',
|
|
to: '[email protected]',
|
|
subject: '测试发信',
|
|
text: 'SMTP 提交链路测试。',
|
|
date: new Date(now - 1000 * 60 * 60 * 22).toISOString(),
|
|
unread: false,
|
|
}),
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
export function makeMessage(input) {
|
|
const text = input.text || ''
|
|
return {
|
|
id: input.id || crypto.randomUUID(),
|
|
folder: input.folder || 'inbox',
|
|
from: input.from || '',
|
|
to: input.to || '',
|
|
subject: input.subject || '(无主题)',
|
|
text,
|
|
html: input.html || '',
|
|
preview: input.preview || text.replace(/\s+/g, ' ').trim().slice(0, 140),
|
|
date: input.date || new Date().toISOString(),
|
|
unread: input.unread ?? true,
|
|
attachments: input.attachments || [],
|
|
messageId: input.messageId || '',
|
|
}
|
|
}
|
|
|
|
export async function listMessages(folder = 'inbox', query = '') {
|
|
const messages = await readMessages()
|
|
const normalized = query.trim().toLowerCase()
|
|
return messages
|
|
.filter((message) => message.folder === folder)
|
|
.filter((message) => {
|
|
if (!normalized) return true
|
|
return [message.from, message.to, message.subject, message.text]
|
|
.join(' ')
|
|
.toLowerCase()
|
|
.includes(normalized)
|
|
})
|
|
.sort((a, b) => new Date(b.date) - new Date(a.date))
|
|
.map(({ text, html, ...summary }) => summary)
|
|
}
|
|
|
|
export async function getMessage(id) {
|
|
const messages = await readMessages()
|
|
return messages.find((message) => message.id === id) || null
|
|
}
|
|
|
|
export async function saveMessage(input) {
|
|
const messages = await readMessages()
|
|
const message = makeMessage(input)
|
|
messages.push(message)
|
|
await queueWrite(messages)
|
|
return message
|
|
}
|
|
|
|
export async function markRead(id) {
|
|
const messages = await readMessages()
|
|
const index = messages.findIndex((message) => message.id === id)
|
|
if (index < 0) return null
|
|
messages[index].unread = false
|
|
await queueWrite(messages)
|
|
return messages[index]
|
|
}
|
|
|
|
export async function mailboxStats() {
|
|
const messages = await readMessages()
|
|
return {
|
|
inbox: messages.filter((message) => message.folder === 'inbox').length,
|
|
unread: messages.filter((message) => message.folder === 'inbox' && message.unread).length,
|
|
sent: messages.filter((message) => message.folder === 'sent').length,
|
|
drafts: messages.filter((message) => message.folder === 'drafts').length,
|
|
}
|
|
}
|