mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-07-11 07:21:28 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0e870ce54 | ||
|
|
90e80fee53 | ||
|
|
4fd7f776f6 | ||
|
|
c73c86e86c |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
|||||||
# CHANGE LOG
|
# CHANGE LOG
|
||||||
|
|
||||||
|
## v0.3.0
|
||||||
|
|
||||||
|
Breaking Changes:
|
||||||
|
|
||||||
|
DB changes:
|
||||||
|
|
||||||
|
`address` 表的前缀将从代码中迁移到 db 中,请将下面 sql 中的 `tmp` 替换为你的前缀,然后执行。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
update
|
||||||
|
address
|
||||||
|
set
|
||||||
|
name = 'tmp' || name;
|
||||||
|
```
|
||||||
|
|
||||||
|
## v0.2.10
|
||||||
|
|
||||||
|
- `ENABLE_USER_DELETE_EMAIL` 是否允许用户删除账户和邮件
|
||||||
|
- `ENABLE_AUTO_REPLY` 是否启用自动回复
|
||||||
|
- fetchAddressError 提示改进
|
||||||
|
- 自动刷新显示倒计时
|
||||||
|
|
||||||
|
* feat: docs update by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/165
|
||||||
|
* feat: add ENABLE_USER_DELETE_EMAIL && ENABLE_AUTO_REPLY && modify fetchAddressError i18n && UI: show autoRefreshInterval by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/169
|
||||||
|
|
||||||
## v0.2.9
|
## v0.2.9
|
||||||
|
|
||||||
- 添加富文本编辑器
|
- 添加富文本编辑器
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { watch, onMounted, ref } from "vue";
|
|||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useGlobalState } from '../store'
|
import { useGlobalState } from '../store'
|
||||||
import { api } from '../api'
|
|
||||||
import { CloudDownloadRound } from '@vicons/material'
|
import { CloudDownloadRound } from '@vicons/material'
|
||||||
import { useIsMobile } from '../utils/composables'
|
import { useIsMobile } from '../utils/composables'
|
||||||
import { processItem, getDownloadEmlUrl } from '../utils/email-parser'
|
import { processItem, getDownloadEmlUrl } from '../utils/email-parser'
|
||||||
@@ -11,7 +10,30 @@ import { processItem, getDownloadEmlUrl } from '../utils/email-parser'
|
|||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
const { settings, openSettings, themeSwitch } = useGlobalState()
|
const props = defineProps({
|
||||||
|
enableUserDeleteEmail: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
requried: false
|
||||||
|
},
|
||||||
|
showEMailTo: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
requried: false
|
||||||
|
},
|
||||||
|
fetchMailData: {
|
||||||
|
type: Function,
|
||||||
|
default: () => { },
|
||||||
|
requried: true
|
||||||
|
},
|
||||||
|
deleteMail: {
|
||||||
|
type: Function,
|
||||||
|
default: () => { },
|
||||||
|
requried: false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { themeSwitch } = useGlobalState()
|
||||||
const autoRefresh = ref(false)
|
const autoRefresh = ref(false)
|
||||||
const autoRefreshInterval = ref(30)
|
const autoRefreshInterval = ref(30)
|
||||||
const data = ref([])
|
const data = ref([])
|
||||||
@@ -29,6 +51,7 @@ const { t } = useI18n({
|
|||||||
locale: 'zh',
|
locale: 'zh',
|
||||||
messages: {
|
messages: {
|
||||||
en: {
|
en: {
|
||||||
|
success: 'Success',
|
||||||
autoRefresh: 'Auto Refresh',
|
autoRefresh: 'Auto Refresh',
|
||||||
refreshAfter: 'Refresh After {msg} Seconds',
|
refreshAfter: 'Refresh After {msg} Seconds',
|
||||||
refresh: 'Refresh',
|
refresh: 'Refresh',
|
||||||
@@ -39,6 +62,7 @@ const { t } = useI18n({
|
|||||||
deleteMailTip: 'Are you sure you want to delete this mail?'
|
deleteMailTip: 'Are you sure you want to delete this mail?'
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
|
success: '成功',
|
||||||
autoRefresh: '自动刷新',
|
autoRefresh: '自动刷新',
|
||||||
refreshAfter: '{msg}秒后刷新',
|
refreshAfter: '{msg}秒后刷新',
|
||||||
refresh: '刷新',
|
refresh: '刷新',
|
||||||
@@ -80,10 +104,8 @@ watch([page, pageSize], async ([page, pageSize], [oldPage, oldPageSize]) => {
|
|||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
try {
|
try {
|
||||||
const { results, count: totalCount } = await api.fetch(
|
const { results, count: totalCount } = await props.fetchMailData(
|
||||||
`/api/mails`
|
pageSize.value, (page.value - 1) * pageSize.value
|
||||||
+ `?limit=${pageSize.value}`
|
|
||||||
+ `&offset=${(page.value - 1) * pageSize.value}`
|
|
||||||
);
|
);
|
||||||
data.value = await Promise.all(results.map(async (item) => {
|
data.value = await Promise.all(results.map(async (item) => {
|
||||||
return await processItem(item);
|
return await processItem(item);
|
||||||
@@ -115,9 +137,7 @@ const mailItemClass = (row) => {
|
|||||||
|
|
||||||
const deleteMail = async () => {
|
const deleteMail = async () => {
|
||||||
try {
|
try {
|
||||||
await api.fetch(`/api/mails/${curMail.value.id}`, {
|
await props.deleteMail(curMail.value.id);
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
message.success(t("success"));
|
message.success(t("success"));
|
||||||
curMail.value = null;
|
curMail.value = null;
|
||||||
await refresh();
|
await refresh();
|
||||||
@@ -133,48 +153,129 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<n-layout v-if="settings.address">
|
<n-split class="left" v-if="!isMobile" direction="horizontal" :max="0.75" :min="0.25" :default-size="0.3">
|
||||||
<n-split class="left" v-if="!isMobile" direction="horizontal" :max="0.75" :min="0.25" :default-size="0.3">
|
<template #1>
|
||||||
<template #1>
|
<div class="center">
|
||||||
<div class="center">
|
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
||||||
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
||||||
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
</div>
|
||||||
</div>
|
<n-switch v-model:value="autoRefresh" size="small" :round="false">
|
||||||
<n-switch v-model:value="autoRefresh" size="small" :round="false">
|
<template #checked>
|
||||||
<template #checked>
|
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
|
||||||
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
|
</template>
|
||||||
|
<template #unchecked>
|
||||||
|
{{ t('autoRefresh') }}
|
||||||
|
</template>
|
||||||
|
</n-switch>
|
||||||
|
<n-button @click="refresh" size="small" type="primary">
|
||||||
|
{{ t('refresh') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div style="overflow: auto; height: 80vh;">
|
||||||
|
<n-list hoverable clickable>
|
||||||
|
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)"
|
||||||
|
:class="mailItemClass(row)">
|
||||||
|
<n-thing :title="row.subject">
|
||||||
|
<template #description>
|
||||||
|
<n-tag type="info">
|
||||||
|
ID: {{ row.id }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
{{ row.created_at }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
FROM: {{ row.source }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag v-if="showEMailTo" type="info">
|
||||||
|
TO: {{ row.address }}
|
||||||
|
</n-tag>
|
||||||
|
</template>
|
||||||
|
</n-thing>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #2>
|
||||||
|
<n-card v-if="curMail" class="mail-item" :title="curMail.subject" style="overflow: auto; max-height: 100vh;">
|
||||||
|
<n-space>
|
||||||
|
<n-tag type="info">
|
||||||
|
ID: {{ curMail.id }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
{{ curMail.created_at }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
FROM: {{ curMail.source }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag v-if="showEMailTo" type="info">
|
||||||
|
TO: {{ curMail.address }}
|
||||||
|
</n-tag>
|
||||||
|
<n-popconfirm v-if="enableUserDeleteEmail" @positive-click="deleteMail">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button tertiary type="error" size="small">{{ t('delete') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
<template #unchecked>
|
{{ t('deleteMailTip') }}
|
||||||
{{ t('autoRefresh') }}
|
</n-popconfirm>
|
||||||
</template>
|
<n-button v-if="curMail.attachments && curMail.attachments.length > 0" size="small" tertiary type="info"
|
||||||
</n-switch>
|
@click="getAttachments(curMail.attachments)">
|
||||||
<n-button @click="refresh" size="small" type="primary">
|
{{ t('attachments') }}
|
||||||
{{ t('refresh') }}
|
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
<n-button tag="a" target="_blank" tertiary type="info" size="small" :download="curMail.id + '.eml'"
|
||||||
<div style="overflow: auto; height: 80vh;">
|
:href="getDownloadEmlUrl(curMail.raw)">
|
||||||
<n-list hoverable clickable>
|
<n-icon :component="CloudDownloadRound" />
|
||||||
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)"
|
{{ t('downloadMail') }}
|
||||||
:class="mailItemClass(row)">
|
</n-button>
|
||||||
<n-thing :title="row.subject">
|
</n-space>
|
||||||
<template #description>
|
<div v-html="curMail.message" style="margin-top: 10px;"></div>
|
||||||
<n-tag type="info">
|
<!-- <iframe :srcdoc="curMail.message" style="width: 100%; height: 100%;"></iframe> -->
|
||||||
ID: {{ row.id }}
|
</n-card>
|
||||||
</n-tag>
|
<n-card class="mail-item" v-else>
|
||||||
<n-tag type="info">
|
<n-result status="info" :title="t('pleaseSelectMail')">
|
||||||
{{ row.created_at }}
|
</n-result>
|
||||||
</n-tag>
|
</n-card>
|
||||||
<div style="word-break: break-all; font-size: small;">
|
</template>
|
||||||
FROM: {{ row.source }}
|
</n-split>
|
||||||
</div>
|
<div class="left" v-else>
|
||||||
</template>
|
<div class="center">
|
||||||
</n-thing>
|
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
||||||
</n-list-item>
|
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
||||||
</n-list>
|
</div>
|
||||||
</div>
|
<n-switch v-model:value="autoRefresh" size="small">
|
||||||
</template>
|
<template #checked>
|
||||||
<template #2>
|
{{ t('autoRefresh') }}
|
||||||
<n-card v-if="curMail" class="mail-item" :title="curMail.subject" style="overflow: auto; max-height: 100vh;">
|
</template>
|
||||||
|
<template #unchecked>
|
||||||
|
{{ t('autoRefresh') }}
|
||||||
|
</template></n-switch>
|
||||||
|
<n-button @click="refresh" size="small" type="primary">
|
||||||
|
{{ t('refresh') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div id="drawer-target" style="overflow: auto; height: 80vh;">
|
||||||
|
<n-list hoverable clickable>
|
||||||
|
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)">
|
||||||
|
<n-thing :title="row.subject">
|
||||||
|
<template #description>
|
||||||
|
<n-tag type="info">
|
||||||
|
ID: {{ row.id }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
{{ row.created_at }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
FROM: {{ row.source }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag v-if="showEMailTo" type="info">
|
||||||
|
TO: {{ row.address }}
|
||||||
|
</n-tag>
|
||||||
|
</template>
|
||||||
|
</n-thing>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</div>
|
||||||
|
<n-drawer v-model:show="curMail" width="100%" :trap-focus="false" :block-scroll="false" to="#drawer-target">
|
||||||
|
<n-drawer-content :title="curMail ? curMail.subject : ''" closable>
|
||||||
|
<n-card style="overflow: auto;">
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-tag type="info">
|
<n-tag type="info">
|
||||||
ID: {{ curMail.id }}
|
ID: {{ curMail.id }}
|
||||||
@@ -185,7 +286,10 @@ onMounted(async () => {
|
|||||||
<n-tag type="info">
|
<n-tag type="info">
|
||||||
FROM: {{ curMail.source }}
|
FROM: {{ curMail.source }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<n-popconfirm v-if="openSettings.enableUserDeleteEmail" @positive-click="deleteMail">
|
<n-tag v-if="showEMailTo" type="info">
|
||||||
|
TO: {{ curMail.address }}
|
||||||
|
</n-tag>
|
||||||
|
<n-popconfirm v-if="enableUserDeleteEmail" @positive-click="deleteMail">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button tertiary type="error" size="small">{{ t('delete') }}</n-button>
|
<n-button tertiary type="error" size="small">{{ t('delete') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -196,89 +300,16 @@ onMounted(async () => {
|
|||||||
{{ t('attachments') }}
|
{{ t('attachments') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button tag="a" target="_blank" tertiary type="info" size="small" :download="curMail.id + '.eml'"
|
<n-button tag="a" target="_blank" tertiary type="info" size="small" :download="curMail.id + '.eml'"
|
||||||
:href="getDownloadEmlUrl(curMail.raw)">
|
:href="getDownloadEmlUrl(curMail)">
|
||||||
<n-icon :component="CloudDownloadRound" />
|
<n-icon :component="CloudDownloadRound" />
|
||||||
{{ t('downloadMail') }}
|
{{ t('downloadMail') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
<div v-html="curMail.message" style="margin-top: 10px;"></div>
|
<div v-html="curMail.message" style="margin-top: 10px;"></div>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="mail-item" v-else>
|
</n-drawer-content>
|
||||||
<n-result status="info" :title="t('pleaseSelectMail')">
|
</n-drawer>
|
||||||
</n-result>
|
</div>
|
||||||
</n-card>
|
|
||||||
</template>
|
|
||||||
</n-split>
|
|
||||||
<div class="left" v-else>
|
|
||||||
<div class="center">
|
|
||||||
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
|
||||||
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
|
||||||
</div>
|
|
||||||
<n-switch v-model:value="autoRefresh" size="small">
|
|
||||||
<template #checked>
|
|
||||||
{{ t('autoRefresh') }}
|
|
||||||
</template>
|
|
||||||
<template #unchecked>
|
|
||||||
{{ t('autoRefresh') }}
|
|
||||||
</template></n-switch>
|
|
||||||
<n-button @click="refresh" size="small" type="primary">
|
|
||||||
{{ t('refresh') }}
|
|
||||||
</n-button>
|
|
||||||
</div>
|
|
||||||
<div id="drawer-target" style="overflow: auto; height: 80vh;">
|
|
||||||
<n-list hoverable clickable>
|
|
||||||
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)">
|
|
||||||
<n-thing :title="row.subject">
|
|
||||||
<template #description>
|
|
||||||
<n-tag type="info">
|
|
||||||
ID: {{ row.id }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
{{ row.created_at }}
|
|
||||||
</n-tag>
|
|
||||||
<div style="word-break: break-all; font-size: small;">
|
|
||||||
FROM: {{ row.source }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</n-thing>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</div>
|
|
||||||
<n-drawer v-model:show="curMail" width="100%" :trap-focus="false" :block-scroll="false" to="#drawer-target">
|
|
||||||
<n-drawer-content :title="curMail.subject" closable>
|
|
||||||
<n-card style="overflow: auto;">
|
|
||||||
<n-space>
|
|
||||||
<n-tag type="info">
|
|
||||||
ID: {{ curMail.id }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
{{ curMail.created_at }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
FROM: {{ curMail.source }}
|
|
||||||
</n-tag>
|
|
||||||
<n-popconfirm @positive-click="deleteMail">
|
|
||||||
<template #trigger>
|
|
||||||
<n-button tertiary type="error" size="small">{{ t('delete') }}</n-button>
|
|
||||||
</template>
|
|
||||||
{{ t('deleteMailTip') }}
|
|
||||||
</n-popconfirm>
|
|
||||||
<n-button v-if="curMail.attachments && curMail.attachments.length > 0" size="small" tertiary type="info"
|
|
||||||
@click="getAttachments(curMail.attachments)">
|
|
||||||
{{ t('attachments') }}
|
|
||||||
</n-button>
|
|
||||||
<n-button tag="a" target="_blank" tertiary type="info" size="small" :download="curMail.id + '.eml'"
|
|
||||||
:href="getDownloadEmlUrl(curMail)">
|
|
||||||
<n-icon :component="CloudDownloadRound" />
|
|
||||||
{{ t('downloadMail') }}
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
<div v-html="curMail.message" style="margin-top: 10px;"></div>
|
|
||||||
</n-card>
|
|
||||||
</n-drawer-content>
|
|
||||||
</n-drawer>
|
|
||||||
</div>
|
|
||||||
</n-layout>
|
|
||||||
<n-modal v-model:show="showAttachments" preset="dialog" title="Dialog">
|
<n-modal v-model:show="showAttachments" preset="dialog" title="Dialog">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div>{{ t("attachments") }}</div>
|
<div>{{ t("attachments") }}</div>
|
||||||
@@ -302,8 +333,6 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
</n-list-item>
|
</n-list-item>
|
||||||
</n-list>
|
</n-list>
|
||||||
<template #action>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import MailBox from './MailBox.vue';
|
import MailBox from '../components/MailBox.vue';
|
||||||
import { useGlobalState } from '../store'
|
import { useGlobalState } from '../store'
|
||||||
const { settings } = useGlobalState()
|
import { api } from '../api'
|
||||||
|
|
||||||
|
const { settings, openSettings } = useGlobalState()
|
||||||
|
|
||||||
|
const fetchMailData = async (limit, offset) => {
|
||||||
|
return await api.fetch(`/api/mails?limit=${limit}&offset=${offset}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMail = async (curMailId) => {
|
||||||
|
await api.fetch(`/api/mails/${curMailId}`, { method: 'DELETE' });
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="settings.address">
|
||||||
<MailBox v-if="settings.address" />
|
<MailBox :showEMailTo="false" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
||||||
|
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, h, onMounted, watch } from 'vue';
|
import { ref, h, onMounted, watch } from 'vue';
|
||||||
|
import { NBadge } from 'naive-ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import { useGlobalState } from '../../store'
|
import { useGlobalState } from '../../store'
|
||||||
import { api } from '../../api'
|
import { api } from '../../api'
|
||||||
import { NMenu } from 'naive-ui';
|
import { NButton, NMenu } from 'naive-ui';
|
||||||
import { MenuFilled } from '@vicons/material'
|
import { MenuFilled } from '@vicons/material'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -19,6 +20,9 @@ const { t } = useI18n({
|
|||||||
en: {
|
en: {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
created_at: 'Created At',
|
created_at: 'Created At',
|
||||||
|
updated_at: 'Update At',
|
||||||
|
mail_count: 'Mail Count',
|
||||||
|
send_count: 'Send Count',
|
||||||
showPass: 'Show Passwrod',
|
showPass: 'Show Passwrod',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
passwordTip: 'Please copy the password and you can use it to login to your email account.',
|
passwordTip: 'Please copy the password and you can use it to login to your email account.',
|
||||||
@@ -35,6 +39,9 @@ const { t } = useI18n({
|
|||||||
zh: {
|
zh: {
|
||||||
name: '名称',
|
name: '名称',
|
||||||
created_at: '创建时间',
|
created_at: '创建时间',
|
||||||
|
updated_at: '更新时间',
|
||||||
|
mail_count: '邮件数量',
|
||||||
|
send_count: '发送数量',
|
||||||
showPass: '显示密码',
|
showPass: '显示密码',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
passwordTip: '请复制密码,你可以使用它登录你的邮箱。',
|
passwordTip: '请复制密码,你可以使用它登录你的邮箱。',
|
||||||
@@ -116,6 +123,62 @@ const columns = [
|
|||||||
title: t('created_at'),
|
title: t('created_at'),
|
||||||
key: "created_at"
|
key: "created_at"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('updated_at'),
|
||||||
|
key: "updated_at"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('mail_count'),
|
||||||
|
key: "mail_count",
|
||||||
|
render(row) {
|
||||||
|
return h(NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
onClick: () => {
|
||||||
|
if (row.mail_count > 0) {
|
||||||
|
adminMailTabAddress.value = row.name;
|
||||||
|
adminTab.value = "mails";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () => h(NBadge, {
|
||||||
|
value: row.mail_count,
|
||||||
|
'show-zero': true,
|
||||||
|
max: 99,
|
||||||
|
type: "success"
|
||||||
|
}),
|
||||||
|
default: () => row.mail_count > 0 ? t('viewMails') : ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('send_count'),
|
||||||
|
key: "send_count",
|
||||||
|
render(row) {
|
||||||
|
return h(NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
onClick: () => {
|
||||||
|
if (row.send_count > 0) {
|
||||||
|
adminSendBoxTabAddress.value = row.name;
|
||||||
|
adminTab.value = "sendBox";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () => h(NBadge, {
|
||||||
|
value: row.send_count,
|
||||||
|
'show-zero': true,
|
||||||
|
max: 99,
|
||||||
|
type: "success"
|
||||||
|
}),
|
||||||
|
default: () => row.send_count > 0 ? t('viewSendBox') : ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('actions'),
|
title: t('actions'),
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
@@ -132,8 +195,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
label: () => h(NButton,
|
label: () => h(NButton,
|
||||||
{
|
{
|
||||||
bordered: false,
|
text: true,
|
||||||
ghost: true,
|
|
||||||
onClick: () => showPassword(row.id)
|
onClick: () => showPassword(row.id)
|
||||||
},
|
},
|
||||||
{ default: () => t('showPass') }
|
{ default: () => t('showPass') }
|
||||||
@@ -142,8 +204,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
label: () => h(NButton,
|
label: () => h(NButton,
|
||||||
{
|
{
|
||||||
bordered: false,
|
text: true,
|
||||||
ghost: true,
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
adminMailTabAddress.value = row.name;
|
adminMailTabAddress.value = row.name;
|
||||||
adminTab.value = "mails";
|
adminTab.value = "mails";
|
||||||
@@ -155,8 +216,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
label: () => h(NButton,
|
label: () => h(NButton,
|
||||||
{
|
{
|
||||||
bordered: false,
|
text: true,
|
||||||
ghost: true,
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
adminSendBoxTabAddress.value = row.name;
|
adminSendBoxTabAddress.value = row.name;
|
||||||
adminTab.value = "sendBox";
|
adminTab.value = "sendBox";
|
||||||
@@ -168,8 +228,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
label: () => h(NButton,
|
label: () => h(NButton,
|
||||||
{
|
{
|
||||||
bordered: false,
|
text: true,
|
||||||
ghost: true,
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
curDeleteAddressId.value = row.id;
|
curDeleteAddressId.value = row.id;
|
||||||
showDelteAccount.value = true;
|
showDelteAccount.value = true;
|
||||||
|
|||||||
@@ -1,63 +1,43 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import { useGlobalState } from '../../store'
|
import { useGlobalState } from '../../store'
|
||||||
import { api } from '../../api'
|
import { api } from '../../api'
|
||||||
import { processItem } from '../../utils/email-parser'
|
import MailBox from '../../components/MailBox.vue';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
localeCache, adminAuth, showAdminAuth,
|
localeCache, adminAuth, showAdminAuth,
|
||||||
adminMailTabAddress
|
adminMailTabAddress
|
||||||
} = useGlobalState()
|
} = useGlobalState()
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
const { t } = useI18n({
|
const { t } = useI18n({
|
||||||
locale: localeCache.value || 'zh',
|
locale: localeCache.value || 'zh',
|
||||||
messages: {
|
messages: {
|
||||||
en: {
|
en: {
|
||||||
mails: 'Emails',
|
addressQueryTip: 'Leave blank to query all addresses',
|
||||||
itemCount: 'itemCount',
|
|
||||||
query: 'Query',
|
query: 'Query',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
mails: '邮件',
|
addressQueryTip: '留空查询所有地址',
|
||||||
itemCount: '总数',
|
|
||||||
query: '查询',
|
query: '查询',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mailData = ref([])
|
const mailBoxKey = ref("")
|
||||||
const mailCount = ref(0)
|
|
||||||
const mailPage = ref(1)
|
|
||||||
const mailPageSize = ref(20)
|
|
||||||
|
|
||||||
watch([mailPage, mailPageSize, adminMailTabAddress], async () => {
|
const queryAddress = () => {
|
||||||
await fetchMailData()
|
mailBoxKey.value = adminMailTabAddress.value;
|
||||||
})
|
}
|
||||||
|
|
||||||
const fetchMailData = async () => {
|
const fetchMailData = async (limit, offset) => {
|
||||||
if (!adminMailTabAddress.value) {
|
return await api.fetch(
|
||||||
return
|
`/admin/mails`
|
||||||
}
|
+ `?limit=${limit}`
|
||||||
try {
|
+ `&offset=${offset}`
|
||||||
const { results, count } = await api.fetch(
|
+ (adminMailTabAddress.value ? `&address=${adminMailTabAddress.value}` : '')
|
||||||
`/admin/mails`
|
);
|
||||||
+ `?address=${adminMailTabAddress.value}`
|
|
||||||
+ `&limit=${mailPageSize.value}`
|
|
||||||
+ `&offset=${(mailPage.value - 1) * mailPageSize.value}`
|
|
||||||
);
|
|
||||||
mailData.value = await Promise.all(results.map(async (item) => {
|
|
||||||
return await processItem(item);
|
|
||||||
}));
|
|
||||||
if (count > 0) {
|
|
||||||
mailCount.value = count;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -65,48 +45,17 @@ onMounted(async () => {
|
|||||||
showAdminAuth.value = true;
|
showAdminAuth.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await fetchMailData()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input v-model:value="adminMailTabAddress" />
|
<n-input v-model:value="adminMailTabAddress" :placeholder="t('addressQueryTip')" />
|
||||||
<n-button @click="fetchMailData" type="primary" ghost>
|
<n-button @click="queryAddress" type="primary" ghost>
|
||||||
{{ t('query') }}
|
{{ t('query') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
<n-list hoverable clickable>
|
<MailBox :key="mailBoxKey" :enableUserDeleteEmail="false" :fetchMailData="fetchMailData" />
|
||||||
<div style="display: inline-block; margin-bottom: 10px;">
|
|
||||||
<n-pagination v-model:page="mailPage" v-model:page-size="mailPageSize" :item-count="mailCount" simple>
|
|
||||||
<template #prefix="{ itemCount }">
|
|
||||||
{{ t('itemCount') }}: {{ itemCount }}
|
|
||||||
</template>
|
|
||||||
</n-pagination>
|
|
||||||
</div>
|
|
||||||
<n-list-item v-for="row in mailData" v-bind:key="row.id">
|
|
||||||
<n-thing class="center" :title="row.subject">
|
|
||||||
<template #description>
|
|
||||||
<n-space>
|
|
||||||
<n-tag type="info">
|
|
||||||
FROM: {{ row.source }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
ID: {{ row.id }}
|
|
||||||
</n-tag>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
<div v-html="row.message"></div>
|
|
||||||
</n-thing>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.n-pagination {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,56 +1,18 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
import { useGlobalState } from '../../store'
|
import { useGlobalState } from '../../store'
|
||||||
import { api } from '../../api'
|
import { api } from '../../api'
|
||||||
import { processItem } from '../../utils/email-parser'
|
import MailBox from '../../components/MailBox.vue';
|
||||||
|
|
||||||
const {
|
const { adminAuth, showAdminAuth } = useGlobalState()
|
||||||
localeCache, adminAuth, showAdminAuth
|
|
||||||
} = useGlobalState()
|
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
const { t } = useI18n({
|
const fetchMailUnknowData = async (limit, offset) => {
|
||||||
locale: localeCache.value || 'zh',
|
return await api.fetch(
|
||||||
messages: {
|
`/admin/mails_unknow`
|
||||||
en: {
|
+ `?limit=${limit}`
|
||||||
itemCount: 'itemCount',
|
+ `&offset=${offset}`
|
||||||
refresh: 'Refresh'
|
);
|
||||||
},
|
|
||||||
zh: {
|
|
||||||
itemCount: '总数',
|
|
||||||
refresh: '刷新'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const mailUnknowData = ref([])
|
|
||||||
const mailUnknowCount = ref(0)
|
|
||||||
const mailUnknowPage = ref(1)
|
|
||||||
const mailUnknowPageSize = ref(20)
|
|
||||||
|
|
||||||
watch([mailUnknowPage, mailUnknowPageSize], async () => {
|
|
||||||
await fetchMailUnknowData()
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchMailUnknowData = async () => {
|
|
||||||
try {
|
|
||||||
const { results, count } = await api.fetch(
|
|
||||||
`/admin/mails_unknow`
|
|
||||||
+ `?limit=${mailUnknowPageSize.value}`
|
|
||||||
+ `&offset=${(mailUnknowPage.value - 1) * mailUnknowPage.value}`
|
|
||||||
);
|
|
||||||
mailUnknowData.value = await Promise.all(results.map(async (item) => {
|
|
||||||
return await processItem(item);
|
|
||||||
}));
|
|
||||||
if (count > 0) {
|
|
||||||
mailUnknowCount.value = count;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -58,49 +20,11 @@ onMounted(async () => {
|
|||||||
showAdminAuth.value = true;
|
showAdminAuth.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await fetchMailUnknowData();
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="adminAuth">
|
||||||
<n-button @click="fetchMailUnknowData" type="primary" ghost>
|
<MailBox :enableUserDeleteEmail="false" :fetchMailData="fetchMailUnknowData" />
|
||||||
{{ t('refresh') }}
|
|
||||||
</n-button>
|
|
||||||
<n-list hoverable clickable>
|
|
||||||
<div style="display: inline-block; margin-bottom: 10px;">
|
|
||||||
<n-pagination v-model:page="mailUnknowPage" v-model:page-size="mailUnknowPageSize"
|
|
||||||
:item-count="mailUnknowCount" simple>
|
|
||||||
<template #prefix="{ itemCount }">
|
|
||||||
{{ t('itemCount') }}: {{ itemCount }}
|
|
||||||
</template>
|
|
||||||
</n-pagination>
|
|
||||||
</div>
|
|
||||||
<n-list-item v-for="row in mailUnknowData" v-bind:key="row.id">
|
|
||||||
<n-thing class="center" :title="row.subject">
|
|
||||||
<template #description>
|
|
||||||
<n-space>
|
|
||||||
<n-tag type="info">
|
|
||||||
FROM: {{ row.source }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
ID: {{ row.id }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
TO: {{ row.address }}
|
|
||||||
</n-tag>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
<div v-html="row.message"></div>
|
|
||||||
</n-thing>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.n-pagination {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -44,15 +44,12 @@ const curRow = ref({})
|
|||||||
const showModal = ref(false)
|
const showModal = ref(false)
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!adminSendBoxTabAddress.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const { results, count: addressCount } = await api.fetch(
|
const { results, count: addressCount } = await api.fetch(
|
||||||
`/admin/sendbox`
|
`/admin/sendbox`
|
||||||
+ `?address=${adminSendBoxTabAddress.value}`
|
+ `?limit=${pageSize.value}`
|
||||||
+ `&limit=${pageSize.value}`
|
|
||||||
+ `&offset=${(page.value - 1) * pageSize.value}`
|
+ `&offset=${(page.value - 1) * pageSize.value}`
|
||||||
|
+ (adminSendBoxTabAddress.value ? `&address=${adminSendBoxTabAddress.value}` : '')
|
||||||
);
|
);
|
||||||
data.value = results.map((item) => {
|
data.value = results.map((item) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const { t } = useI18n({
|
|||||||
itemCount: 'itemCount',
|
itemCount: 'itemCount',
|
||||||
modalTip: 'Please input the sender balance',
|
modalTip: 'Please input the sender balance',
|
||||||
balance: 'Balance',
|
balance: 'Balance',
|
||||||
refresh: 'Refresh',
|
query: 'Query',
|
||||||
ok: 'OK'
|
ok: 'OK'
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
@@ -36,7 +36,7 @@ const { t } = useI18n({
|
|||||||
itemCount: '总数',
|
itemCount: '总数',
|
||||||
modalTip: '请输入发件额度',
|
modalTip: '请输入发件额度',
|
||||||
balance: '余额',
|
balance: '余额',
|
||||||
refresh: '刷新',
|
query: '查询',
|
||||||
ok: '确定'
|
ok: '确定'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,7 @@ const showModal = ref(false)
|
|||||||
const senderBalance = ref(0)
|
const senderBalance = ref(0)
|
||||||
const senderEnabled = ref(false)
|
const senderEnabled = ref(false)
|
||||||
|
|
||||||
|
const addressQuery = ref('')
|
||||||
|
|
||||||
const updateData = async () => {
|
const updateData = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -77,6 +78,7 @@ const fetchData = async () => {
|
|||||||
`/admin/address_sender`
|
`/admin/address_sender`
|
||||||
+ `?limit=${pageSize.value}`
|
+ `?limit=${pageSize.value}`
|
||||||
+ `&offset=${(page.value - 1) * pageSize.value}`
|
+ `&offset=${(page.value - 1) * pageSize.value}`
|
||||||
|
+ (addressQuery.value ? `&address=${addressQuery.value}` : '')
|
||||||
);
|
);
|
||||||
data.value = results;
|
data.value = results;
|
||||||
if (addressCount > 0) {
|
if (addressCount > 0) {
|
||||||
@@ -166,17 +168,18 @@ onMounted(async () => {
|
|||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
<n-input-group>
|
||||||
|
<n-input v-model:value="addressQuery" />
|
||||||
|
<n-button @click="fetchData" type="primary" ghost>
|
||||||
|
{{ t('query') }}
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
<div style="display: inline-block;">
|
<div style="display: inline-block;">
|
||||||
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" :page-sizes="[20, 50, 100]"
|
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" :page-sizes="[20, 50, 100]"
|
||||||
show-size-picker>
|
show-size-picker>
|
||||||
<template #prefix="{ itemCount }">
|
<template #prefix="{ itemCount }">
|
||||||
{{ t('itemCount') }}: {{ itemCount }}
|
{{ t('itemCount') }}: {{ itemCount }}
|
||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
|
||||||
<n-button @click="fetchData" type="primary" size="small" ghost>
|
|
||||||
{{ t('refresh') }}
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
</n-pagination>
|
</n-pagination>
|
||||||
</div>
|
</div>
|
||||||
<n-data-table :columns="columns" :data="data" :bordered="false" />
|
<n-data-table :columns="columns" :data="data" :bordered="false" />
|
||||||
|
|||||||
@@ -15,25 +15,31 @@ api.get('/admin/address', async (c) => {
|
|||||||
}
|
}
|
||||||
if (query) {
|
if (query) {
|
||||||
const { results } = await c.env.DB.prepare(
|
const { results } = await c.env.DB.prepare(
|
||||||
`SELECT * FROM address where concat('${c.env.PREFIX}', name) like ? order by id desc limit ? offset ? `
|
`SELECT a.*,`
|
||||||
|
+ ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
|
||||||
|
+ ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
|
||||||
|
+ ` FROM address a`
|
||||||
|
+ ` where name like ?`
|
||||||
|
+ ` order by id desc limit ? offset ?`
|
||||||
).bind(`%${query}%`, limit, offset).all();
|
).bind(`%${query}%`, limit, offset).all();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
const { count: addressCount } = await c.env.DB.prepare(
|
const { count: addressCount } = await c.env.DB.prepare(
|
||||||
`SELECT count(*) as count FROM address where concat('${c.env.PREFIX}', name) like ?`
|
`SELECT count(*) as count FROM address where name like ?`
|
||||||
).bind(`%${query}%`).first();
|
).bind(`%${query}%`).first();
|
||||||
count = addressCount;
|
count = addressCount;
|
||||||
}
|
}
|
||||||
return c.json({
|
return c.json({
|
||||||
results: results.map((r) => {
|
results: results,
|
||||||
r.name = c.env.PREFIX + r.name;
|
|
||||||
return r;
|
|
||||||
}),
|
|
||||||
count: count
|
count: count
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const { results } = await c.env.DB.prepare(
|
const { results } = await c.env.DB.prepare(
|
||||||
`SELECT * FROM address order by id desc limit ? offset ? `
|
`SELECT a.*,`
|
||||||
|
+ ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
|
||||||
|
+ ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
|
||||||
|
+ ` FROM address a`
|
||||||
|
+ ` order by id desc limit ? offset ?`
|
||||||
).bind(limit, offset).all();
|
).bind(limit, offset).all();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
@@ -43,10 +49,7 @@ api.get('/admin/address', async (c) => {
|
|||||||
count = addressCount;
|
count = addressCount;
|
||||||
}
|
}
|
||||||
return c.json({
|
return c.json({
|
||||||
results: results.map((r) => {
|
results: results,
|
||||||
r.name = c.env.PREFIX + r.name;
|
|
||||||
return r;
|
|
||||||
}),
|
|
||||||
count: count
|
count: count
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -61,7 +64,7 @@ api.delete('/admin/delete_address/:id', async (c) => {
|
|||||||
}
|
}
|
||||||
const { success: mailSuccess } = await c.env.DB.prepare(
|
const { success: mailSuccess } = await c.env.DB.prepare(
|
||||||
`DELETE FROM mails WHERE address IN
|
`DELETE FROM mails WHERE address IN
|
||||||
(select concat('${c.env.PREFIX}', name) from address where id = ?) `
|
(select name from address where id = ?) `
|
||||||
).bind(id).run();
|
).bind(id).run();
|
||||||
if (!mailSuccess) {
|
if (!mailSuccess) {
|
||||||
return c.text("Failed to delete mails", 500)
|
return c.text("Failed to delete mails", 500)
|
||||||
@@ -79,10 +82,8 @@ api.get('/admin/show_password/:id', async (c) => {
|
|||||||
const name = await c.env.DB.prepare(
|
const name = await c.env.DB.prepare(
|
||||||
`SELECT name FROM address WHERE id = ? `
|
`SELECT name FROM address WHERE id = ? `
|
||||||
).bind(id).first("name");
|
).bind(id).first("name");
|
||||||
// compute address
|
|
||||||
const emailAddress = c.env.PREFIX + name
|
|
||||||
const jwt = await Jwt.sign({
|
const jwt = await Jwt.sign({
|
||||||
address: emailAddress,
|
address: name,
|
||||||
address_id: id
|
address_id: id
|
||||||
}, c.env.JWT_SECRET)
|
}, c.env.JWT_SECRET)
|
||||||
return c.json({
|
return c.json({
|
||||||
@@ -99,6 +100,22 @@ api.get('/admin/mails', async (c) => {
|
|||||||
if (!offset || offset < 0) {
|
if (!offset || offset < 0) {
|
||||||
return c.text("Invalid offset", 400)
|
return c.text("Invalid offset", 400)
|
||||||
}
|
}
|
||||||
|
if (!address) {
|
||||||
|
const { results } = await c.env.DB.prepare(
|
||||||
|
`SELECT * FROM raw_mails order by id desc limit ? offset ?`
|
||||||
|
).bind(limit, offset).all();
|
||||||
|
let count = 0;
|
||||||
|
if (offset == 0) {
|
||||||
|
const { count: mailCount } = await c.env.DB.prepare(
|
||||||
|
`SELECT count(*) as count FROM raw_mails`
|
||||||
|
).first();
|
||||||
|
count = mailCount;
|
||||||
|
}
|
||||||
|
return c.json({
|
||||||
|
results: results,
|
||||||
|
count: count
|
||||||
|
})
|
||||||
|
}
|
||||||
const { results } = await c.env.DB.prepare(
|
const { results } = await c.env.DB.prepare(
|
||||||
`SELECT * FROM raw_mails where address = ? order by id desc limit ? offset ?`
|
`SELECT * FROM raw_mails where address = ? order by id desc limit ? offset ?`
|
||||||
).bind(address, limit, offset).all();
|
).bind(address, limit, offset).all();
|
||||||
@@ -125,7 +142,7 @@ api.get('/admin/mails_unknow', async (c) => {
|
|||||||
}
|
}
|
||||||
const { results } = await c.env.DB.prepare(`
|
const { results } = await c.env.DB.prepare(`
|
||||||
SELECT * FROM raw_mails
|
SELECT * FROM raw_mails
|
||||||
where address NOT IN(select concat('${c.env.PREFIX}', name) from address)
|
where address NOT IN (select name from address)
|
||||||
order by id desc limit ? offset ? `
|
order by id desc limit ? offset ? `
|
||||||
).bind(limit, offset).all();
|
).bind(limit, offset).all();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@@ -133,7 +150,7 @@ api.get('/admin/mails_unknow', async (c) => {
|
|||||||
const { count: mailCount } = await c.env.DB.prepare(`
|
const { count: mailCount } = await c.env.DB.prepare(`
|
||||||
SELECT count(*) as count FROM raw_mails
|
SELECT count(*) as count FROM raw_mails
|
||||||
where address NOT IN
|
where address NOT IN
|
||||||
(select concat('${c.env.PREFIX}', name) from address)`
|
(select name from address)`
|
||||||
).first();
|
).first();
|
||||||
count = mailCount;
|
count = mailCount;
|
||||||
}
|
}
|
||||||
@@ -144,13 +161,29 @@ api.get('/admin/mails_unknow', async (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
api.get('/admin/address_sender', async (c) => {
|
api.get('/admin/address_sender', async (c) => {
|
||||||
const { limit, offset } = c.req.query();
|
const { address, limit, offset } = c.req.query();
|
||||||
if (!limit || limit < 0 || limit > 100) {
|
if (!limit || limit < 0 || limit > 100) {
|
||||||
return c.text("Invalid limit", 400)
|
return c.text("Invalid limit", 400)
|
||||||
}
|
}
|
||||||
if (!offset || offset < 0) {
|
if (!offset || offset < 0) {
|
||||||
return c.text("Invalid offset", 400)
|
return c.text("Invalid offset", 400)
|
||||||
}
|
}
|
||||||
|
if (address) {
|
||||||
|
const { results } = await c.env.DB.prepare(
|
||||||
|
`SELECT * FROM address_sender where address = ? order by id desc limit ? offset ?`
|
||||||
|
).bind(address, limit, offset).all();
|
||||||
|
let count = 0;
|
||||||
|
if (offset == 0) {
|
||||||
|
const { count: addressCount } = await c.env.DB.prepare(
|
||||||
|
`SELECT count(*) as count FROM address_sender where address = ?`
|
||||||
|
).bind(address).first();
|
||||||
|
count = addressCount;
|
||||||
|
}
|
||||||
|
return c.json({
|
||||||
|
results: results,
|
||||||
|
count: count
|
||||||
|
})
|
||||||
|
}
|
||||||
const { results } = await c.env.DB.prepare(
|
const { results } = await c.env.DB.prepare(
|
||||||
`SELECT * FROM address_sender order by id desc limit ? offset ? `
|
`SELECT * FROM address_sender order by id desc limit ? offset ? `
|
||||||
).bind(limit, offset).all();
|
).bind(limit, offset).all();
|
||||||
@@ -190,7 +223,42 @@ api.post('/admin/address_sender', async (c) => {
|
|||||||
|
|
||||||
api.get('/admin/sendbox', async (c) => {
|
api.get('/admin/sendbox', async (c) => {
|
||||||
const { address, limit, offset } = c.req.query();
|
const { address, limit, offset } = c.req.query();
|
||||||
return getSendbox(c, address, limit, offset);
|
if (!limit || limit < 0 || limit > 100) {
|
||||||
|
return c.text("Invalid limit", 400)
|
||||||
|
}
|
||||||
|
if (!offset || offset < 0) {
|
||||||
|
return c.text("Invalid offset", 400)
|
||||||
|
}
|
||||||
|
if (!address) {
|
||||||
|
const { results } = await c.env.DB.prepare(
|
||||||
|
`SELECT * FROM sendbox order by id desc limit ? offset ?`
|
||||||
|
).bind(limit, offset).all();
|
||||||
|
let count = 0;
|
||||||
|
if (offset == 0) {
|
||||||
|
const { count: mailCount } = await c.env.DB.prepare(
|
||||||
|
`SELECT count(*) as count FROM sendbox`
|
||||||
|
).first();
|
||||||
|
count = mailCount;
|
||||||
|
}
|
||||||
|
return c.json({
|
||||||
|
results: results,
|
||||||
|
count: count
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const { results } = await c.env.DB.prepare(
|
||||||
|
`SELECT * FROM sendbox where address = ? order by id desc limit ? offset ?`
|
||||||
|
).bind(address, limit, offset).all();
|
||||||
|
let count = 0;
|
||||||
|
if (offset == 0) {
|
||||||
|
const { count: mailCount } = await c.env.DB.prepare(
|
||||||
|
`SELECT count(*) as count FROM sendbox where address = ?`
|
||||||
|
).bind(address).first();
|
||||||
|
count = mailCount;
|
||||||
|
}
|
||||||
|
return c.json({
|
||||||
|
results: results,
|
||||||
|
count: count
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
api.get('/admin/statistics', async (c) => {
|
api.get('/admin/statistics', async (c) => {
|
||||||
@@ -232,7 +300,7 @@ api.post('/admin/cleanup', async (c) => {
|
|||||||
case "mails_unknow":
|
case "mails_unknow":
|
||||||
await c.env.DB.prepare(`
|
await c.env.DB.prepare(`
|
||||||
DELETE FROM raw_mails WHERE address NOT IN
|
DELETE FROM raw_mails WHERE address NOT IN
|
||||||
(select concat('${c.env.PREFIX}', name) from address) AND created_at < datetime('now', '-${cleanDays} day')`
|
(select name from address) AND created_at < datetime('now', '-${cleanDays} day')`
|
||||||
).run();
|
).run();
|
||||||
break;
|
break;
|
||||||
case "address":
|
case "address":
|
||||||
|
|||||||
@@ -37,15 +37,14 @@ api.get('/admin/v1/mails_unknow', async (c) => {
|
|||||||
}
|
}
|
||||||
const { results } = await c.env.DB.prepare(`
|
const { results } = await c.env.DB.prepare(`
|
||||||
SELECT id, source, subject, message FROM mails
|
SELECT id, source, subject, message FROM mails
|
||||||
where address NOT IN(select concat('${c.env.PREFIX}', name) from address)
|
where address NOT IN(select name from address)
|
||||||
order by id desc limit ? offset ? `
|
order by id desc limit ? offset ? `
|
||||||
).bind(limit, offset).all();
|
).bind(limit, offset).all();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
const { count: mailCount } = await c.env.DB.prepare(`
|
const { count: mailCount } = await c.env.DB.prepare(`
|
||||||
SELECT count(*) as count FROM mails
|
SELECT count(*) as count FROM mails
|
||||||
where address NOT IN
|
where address NOT IN (select name from address)`
|
||||||
(select concat('${c.env.PREFIX}', name) from address)`
|
|
||||||
).first();
|
).first();
|
||||||
count = mailCount;
|
count = mailCount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,54 +7,49 @@ async function email(message, env, ctx) {
|
|||||||
console.log(`Reject message from ${message.from} to ${message.to}`);
|
console.log(`Reject message from ${message.from} to ${message.to}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!env.PREFIX || (message.to && message.to.startsWith(env.PREFIX))) {
|
const rawEmail = await new Response(message.raw).text();
|
||||||
const rawEmail = await new Response(message.raw).text();
|
const message_id = message.headers.get("Message-ID");
|
||||||
const message_id = message.headers.get("Message-ID");
|
// save email
|
||||||
// save email
|
const { success } = await env.DB.prepare(
|
||||||
const { success } = await env.DB.prepare(
|
`INSERT INTO raw_mails (source, address, raw, message_id) VALUES (?, ?, ?, ?)`
|
||||||
`INSERT INTO raw_mails (source, address, raw, message_id) VALUES (?, ?, ?, ?)`
|
).bind(
|
||||||
).bind(
|
message.from, message.to, rawEmail, message_id
|
||||||
message.from, message.to, rawEmail, message_id
|
).run();
|
||||||
).run();
|
if (!success) {
|
||||||
if (!success) {
|
message.setReject(`Failed save message to ${message.to}`);
|
||||||
message.setReject(`Failed save message to ${message.to}`);
|
console.log(`Failed save message from ${message.from} to ${message.to}`);
|
||||||
console.log(`Failed save message from ${message.from} to ${message.to}`);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// auto reply email
|
// auto reply email
|
||||||
if (env.ENABLE_AUTO_REPLY) {
|
if (env.ENABLE_AUTO_REPLY) {
|
||||||
try {
|
try {
|
||||||
const results = await env.DB.prepare(
|
const results = await env.DB.prepare(
|
||||||
`SELECT * FROM auto_reply_mails where address = ? and enabled = 1`
|
`SELECT * FROM auto_reply_mails where address = ? and enabled = 1`
|
||||||
).bind(message.to).first();
|
).bind(message.to).first();
|
||||||
if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) {
|
if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) {
|
||||||
const msg = createMimeMessage();
|
const msg = createMimeMessage();
|
||||||
msg.setHeader("In-Reply-To", message.headers.get("Message-ID"));
|
msg.setHeader("In-Reply-To", message.headers.get("Message-ID"));
|
||||||
msg.setSender({
|
msg.setSender({
|
||||||
name: results.name || results.address,
|
name: results.name || results.address,
|
||||||
addr: results.address
|
addr: results.address
|
||||||
});
|
});
|
||||||
msg.setRecipient(message.from);
|
msg.setRecipient(message.from);
|
||||||
msg.setSubject(results.subject || "Auto-reply");
|
msg.setSubject(results.subject || "Auto-reply");
|
||||||
msg.addMessage({
|
msg.addMessage({
|
||||||
contentType: 'text/plain',
|
contentType: 'text/plain',
|
||||||
data: results.message || "This is an auto-reply message, please reconact later."
|
data: results.message || "This is an auto-reply message, please reconact later."
|
||||||
});
|
});
|
||||||
|
|
||||||
const replyMessage = new EmailMessage(
|
const replyMessage = new EmailMessage(
|
||||||
message.to,
|
message.to,
|
||||||
message.from,
|
message.from,
|
||||||
msg.asRaw()
|
msg.asRaw()
|
||||||
);
|
);
|
||||||
await message.reply(replyMessage);
|
await message.reply(replyMessage);
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("reply email error", error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("reply email error", error);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.setReject(`Unknown address ${message.to}`);
|
|
||||||
console.log(`Unknown address ${message.to}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ api.get('/api/mails', async (c) => {
|
|||||||
return c.text("Invalid offset", 400)
|
return c.text("Invalid offset", 400)
|
||||||
}
|
}
|
||||||
const { results } = await c.env.DB.prepare(
|
const { results } = await c.env.DB.prepare(
|
||||||
`SELECT id, source, raw, created_at FROM raw_mails where address = ? order by id desc limit ? offset ?`
|
`SELECT * FROM raw_mails where address = ? order by id desc limit ? offset ?`
|
||||||
).bind(address, limit, offset).all();
|
).bind(address, limit, offset).all();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
@@ -34,7 +34,7 @@ api.get('/api/mails', async (c) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.delete('/api/mails/:id', async (c) => {
|
api.delete('/api/mails/:id', async (c) => {
|
||||||
if (c.env.ENABLE_USER_DELETE_EMAIL) {
|
if (!c.env.ENABLE_USER_DELETE_EMAIL) {
|
||||||
return c.text("User delete email is disabled", 403)
|
return c.text("User delete email is disabled", 403)
|
||||||
}
|
}
|
||||||
const { address } = c.get("jwtPayload")
|
const { address } = c.get("jwtPayload")
|
||||||
@@ -61,28 +61,26 @@ api.get('/api/settings', async (c) => {
|
|||||||
return c.text("Invalid address", 400)
|
return c.text("Invalid address", 400)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (address.startsWith(c.env.PREFIX)) {
|
// check address id
|
||||||
// check address id
|
try {
|
||||||
try {
|
if (!address_id) {
|
||||||
if (!address_id) {
|
const db_address_id = await c.env.DB.prepare(
|
||||||
const db_address_id = await c.env.DB.prepare(
|
`SELECT id FROM address where name = ?`
|
||||||
`SELECT id FROM address where name = ?`
|
).bind(address).first("id");
|
||||||
).bind(address.substring(c.env.PREFIX.length)).first("id");
|
if (!db_address_id) {
|
||||||
if (!db_address_id) {
|
return c.text("Invalid address", 400)
|
||||||
return c.text("Invalid address", 400)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
return c.text("Invalid address", 400)
|
|
||||||
}
|
|
||||||
// update address updated_at
|
|
||||||
try {
|
|
||||||
c.env.DB.prepare(
|
|
||||||
`UPDATE address SET updated_at = datetime('now') where name = ?`
|
|
||||||
).bind(address.substring(c.env.PREFIX.length)).run();
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Failed to update address")
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return c.text("Invalid address", 400)
|
||||||
|
}
|
||||||
|
// update address updated_at
|
||||||
|
try {
|
||||||
|
c.env.DB.prepare(
|
||||||
|
`UPDATE address SET updated_at = datetime('now') where name = ?`
|
||||||
|
).bind(address).run();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to update address")
|
||||||
}
|
}
|
||||||
let auto_reply = {};
|
let auto_reply = {};
|
||||||
if (c.env.ENABLE_AUTO_REPLY) {
|
if (c.env.ENABLE_AUTO_REPLY) {
|
||||||
@@ -181,11 +179,11 @@ api.get('/api/new_address', async (c) => {
|
|||||||
domain = domains[Math.floor(Math.random() * domains.length)];
|
domain = domains[Math.floor(Math.random() * domains.length)];
|
||||||
}
|
}
|
||||||
// create address
|
// create address
|
||||||
const emailAddress = c.env.PREFIX + name + "@" + domain
|
name = c.env.PREFIX + name + "@" + domain
|
||||||
try {
|
try {
|
||||||
const { success } = await c.env.DB.prepare(
|
const { success } = await c.env.DB.prepare(
|
||||||
`INSERT INTO address(name) VALUES(?)`
|
`INSERT INTO address(name) VALUES(?)`
|
||||||
).bind(name + "@" + domain).run();
|
).bind(name).run();
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return c.text("Failed to create address", 500)
|
return c.text("Failed to create address", 500)
|
||||||
}
|
}
|
||||||
@@ -199,13 +197,13 @@ api.get('/api/new_address', async (c) => {
|
|||||||
try {
|
try {
|
||||||
address_id = await c.env.DB.prepare(
|
address_id = await c.env.DB.prepare(
|
||||||
`SELECT id FROM address where name = ?`
|
`SELECT id FROM address where name = ?`
|
||||||
).bind(name + "@" + domain).first("id");
|
).bind(name).first("id");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
// create jwt
|
// create jwt
|
||||||
const jwt = await Jwt.sign({
|
const jwt = await Jwt.sign({
|
||||||
address: emailAddress,
|
address: name,
|
||||||
address_id: address_id
|
address_id: address_id
|
||||||
}, c.env.JWT_SECRET)
|
}, c.env.JWT_SECRET)
|
||||||
return c.json({
|
return c.json({
|
||||||
@@ -214,14 +212,11 @@ api.get('/api/new_address', async (c) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.delete('/api/delete_address', async (c) => {
|
api.delete('/api/delete_address', async (c) => {
|
||||||
if (c.env.ENABLE_USER_DELETE_EMAIL) {
|
if (!c.env.ENABLE_USER_DELETE_EMAIL) {
|
||||||
return c.text("User delete email is disabled", 403)
|
return c.text("User delete email is disabled", 403)
|
||||||
}
|
}
|
||||||
const { address } = c.get("jwtPayload")
|
const { address } = c.get("jwtPayload")
|
||||||
let name = address;
|
let name = address;
|
||||||
if (address.startsWith(c.env.PREFIX)) {
|
|
||||||
name = address.substring(c.env.PREFIX.length);
|
|
||||||
}
|
|
||||||
const { success } = await c.env.DB.prepare(
|
const { success } = await c.env.DB.prepare(
|
||||||
`DELETE FROM address WHERE name = ? `
|
`DELETE FROM address WHERE name = ? `
|
||||||
).bind(name).run();
|
).bind(name).run();
|
||||||
|
|||||||
Reference in New Issue
Block a user