feat: add Docker deployment and Microsoft OAuth support
This commit is contained in:
@@ -1,109 +1,111 @@
|
||||
const TOKEN_KEY = 'mengpost_token'
|
||||
const EVT = 'mengpost-token'
|
||||
|
||||
/** 生产环境前后端分离时设 VITE_API_URL, 如 http://127.0.0.1:8787 */
|
||||
const base = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '')
|
||||
|
||||
export function apiUrl(path) {
|
||||
if (!path.startsWith('/')) path = '/' + path
|
||||
return base ? base + path : path
|
||||
}
|
||||
|
||||
export const tokenStore = {
|
||||
get: () => localStorage.getItem(TOKEN_KEY) || '',
|
||||
set: (t) => {
|
||||
localStorage.setItem(TOKEN_KEY, t)
|
||||
window.dispatchEvent(new Event(EVT))
|
||||
},
|
||||
clear: () => {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
window.dispatchEvent(new Event(EVT))
|
||||
},
|
||||
subscribe: (fn) => {
|
||||
window.addEventListener(EVT, fn)
|
||||
return () => window.removeEventListener(EVT, fn)
|
||||
},
|
||||
}
|
||||
|
||||
function parseJsonSafe(txt) {
|
||||
if (!txt || !txt.trim()) return null
|
||||
try {
|
||||
return JSON.parse(txt)
|
||||
} catch {
|
||||
return { error: txt.length > 120 ? txt.slice(0, 120) + '…' : txt }
|
||||
}
|
||||
}
|
||||
|
||||
async function request(method, path, body) {
|
||||
const headers = { 'Content-Type': 'application/json' }
|
||||
const t = tokenStore.get()
|
||||
if (t) headers['X-Auth-Token'] = t
|
||||
let res
|
||||
try {
|
||||
res = await fetch(apiUrl(path), {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
e.message?.includes('Failed to fetch')
|
||||
? '无法连接后端:请确认已启动 mengpost-backend (默认 8787),且本页通过 Vite 开发服务器打开或已配置 VITE_API_URL'
|
||||
: e.message,
|
||||
)
|
||||
}
|
||||
const txt = await res.text()
|
||||
const data = parseJsonSafe(txt)
|
||||
if (!res.ok) {
|
||||
const msg = (data && data.error) || res.statusText || '请求失败'
|
||||
throw new Error(msg)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
/** 列表接口在空 body / SW 缓存异常时可能得到 null,统一成数组 */
|
||||
function asArray(v) {
|
||||
return Array.isArray(v) ? v : []
|
||||
}
|
||||
|
||||
/** 校验 token 时不带 X-Auth-Token,避免旧 token 干扰 */
|
||||
export async function verifyToken(token) {
|
||||
let res
|
||||
try {
|
||||
res = await fetch(apiUrl('/api/auth/verify'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token }),
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
e.message?.includes('Failed to fetch')
|
||||
? '无法连接后端:请先运行 dev.bat / dev.sh 启动后端,或设置 VITE_API_URL'
|
||||
: e.message,
|
||||
)
|
||||
}
|
||||
const txt = await res.text()
|
||||
const data = parseJsonSafe(txt)
|
||||
if (!res.ok) {
|
||||
if (data && data.ok === false) return { ok: false }
|
||||
throw new Error((data && data.error) || res.statusText || '验证失败')
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export const api = {
|
||||
listAccounts: () => request('GET', '/api/accounts').then(asArray),
|
||||
createAccount: (a) => request('POST', '/api/accounts', a),
|
||||
updateAccount: (id, a) => request('PUT', `/api/accounts/${id}`, a),
|
||||
deleteAccount: (id) => request('DELETE', `/api/accounts/${id}`),
|
||||
listMailboxes: (id) => request('GET', `/api/accounts/${id}/mailboxes`).then(asArray),
|
||||
listMessages: (id, mailbox = 'INBOX', limit = 30) =>
|
||||
request('GET', `/api/accounts/${id}/messages?mailbox=${encodeURIComponent(mailbox)}&limit=${limit}`).then(
|
||||
asArray,
|
||||
),
|
||||
getMessage: (id, uid, mailbox = 'INBOX') =>
|
||||
request('GET', `/api/accounts/${id}/messages/${uid}?mailbox=${encodeURIComponent(mailbox)}`),
|
||||
send: (id, body) => request('POST', `/api/accounts/${id}/send`, body),
|
||||
listSent: (id, limit = 50) =>
|
||||
request('GET', `/api/accounts/${id}/sent?limit=${limit}`).then(asArray),
|
||||
}
|
||||
const TOKEN_KEY = 'mengpost_token'
|
||||
const EVT = 'mengpost-token'
|
||||
|
||||
/** 生产环境前后端分离时设 VITE_API_URL, 如 http://127.0.0.1:8787 */
|
||||
const base = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '')
|
||||
|
||||
export function apiUrl(path) {
|
||||
if (!path.startsWith('/')) path = '/' + path
|
||||
return base ? base + path : path
|
||||
}
|
||||
|
||||
export const tokenStore = {
|
||||
get: () => localStorage.getItem(TOKEN_KEY) || '',
|
||||
set: (t) => {
|
||||
localStorage.setItem(TOKEN_KEY, t)
|
||||
window.dispatchEvent(new Event(EVT))
|
||||
},
|
||||
clear: () => {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
window.dispatchEvent(new Event(EVT))
|
||||
},
|
||||
subscribe: (fn) => {
|
||||
window.addEventListener(EVT, fn)
|
||||
return () => window.removeEventListener(EVT, fn)
|
||||
},
|
||||
}
|
||||
|
||||
function parseJsonSafe(txt) {
|
||||
if (!txt || !txt.trim()) return null
|
||||
try {
|
||||
return JSON.parse(txt)
|
||||
} catch {
|
||||
return { error: txt.length > 120 ? txt.slice(0, 120) + '…' : txt }
|
||||
}
|
||||
}
|
||||
|
||||
async function request(method, path, body) {
|
||||
const headers = { 'Content-Type': 'application/json' }
|
||||
const t = tokenStore.get()
|
||||
if (t) headers['X-Auth-Token'] = t
|
||||
let res
|
||||
try {
|
||||
res = await fetch(apiUrl(path), {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
e.message?.includes('Failed to fetch')
|
||||
? '无法连接后端:请确认已启动 mengpost-backend (默认 8787),且本页通过 Vite 开发服务器打开或已配置 VITE_API_URL'
|
||||
: e.message,
|
||||
)
|
||||
}
|
||||
const txt = await res.text()
|
||||
const data = parseJsonSafe(txt)
|
||||
if (!res.ok) {
|
||||
const msg = (data && data.error) || res.statusText || '请求失败'
|
||||
throw new Error(msg)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
/** 列表接口在空 body / SW 缓存异常时可能得到 null,统一成数组 */
|
||||
function asArray(v) {
|
||||
return Array.isArray(v) ? v : []
|
||||
}
|
||||
|
||||
/** 校验 token 时不带 X-Auth-Token,避免旧 token 干扰 */
|
||||
export async function verifyToken(token) {
|
||||
let res
|
||||
try {
|
||||
res = await fetch(apiUrl('/api/auth/verify'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token }),
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
e.message?.includes('Failed to fetch')
|
||||
? '无法连接后端:请先运行 dev.bat / dev.sh 启动后端,或设置 VITE_API_URL'
|
||||
: e.message,
|
||||
)
|
||||
}
|
||||
const txt = await res.text()
|
||||
const data = parseJsonSafe(txt)
|
||||
if (!res.ok) {
|
||||
if (data && data.ok === false) return { ok: false }
|
||||
throw new Error((data && data.error) || res.statusText || '验证失败')
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export const api = {
|
||||
listAccounts: () => request('GET', '/api/accounts').then(asArray),
|
||||
createAccount: (a) => request('POST', '/api/accounts', a),
|
||||
updateAccount: (id, a) => request('PUT', `/api/accounts/${id}`, a),
|
||||
deleteAccount: (id) => request('DELETE', `/api/accounts/${id}`),
|
||||
listMailboxes: (id) => request('GET', `/api/accounts/${id}/mailboxes`).then(asArray),
|
||||
listMessages: (id, mailbox = 'INBOX', limit = 30) =>
|
||||
request('GET', `/api/accounts/${id}/messages?mailbox=${encodeURIComponent(mailbox)}&limit=${limit}`).then(
|
||||
asArray,
|
||||
),
|
||||
getMessage: (id, uid, mailbox = 'INBOX') =>
|
||||
request('GET', `/api/accounts/${id}/messages/${uid}?mailbox=${encodeURIComponent(mailbox)}`),
|
||||
send: (id, body) => request('POST', `/api/accounts/${id}/send`, body),
|
||||
listSent: (id, limit = 50) =>
|
||||
request('GET', `/api/accounts/${id}/sent?limit=${limit}`).then(asArray),
|
||||
microsoftOAuthStart: () => request('GET', '/api/oauth/microsoft/start'),
|
||||
microsoftOAuthFinish: (state) => request('POST', '/api/oauth/microsoft/finish', { state }),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user