Compare commits

..

4 Commits

Author SHA1 Message Date
Dream Hunter
2cc84d565c feat: upgrade dependencies (#690) 2025-07-19 13:26:10 +08:00
Dream Hunter
c96d180591 feat(oauth2): add default role assignment for new OAuth2 users (#688)
- Add default role assignment logic in OAuth2 login flow
- Import getStringValue and getUserRoles utilities
- Validate default role exists in system before assignment
- Use ON CONFLICT DO NOTHING to preserve existing user roles
- Add proper error handling for role assignment failures
2025-07-14 23:55:36 +08:00
Dream Hunter
1303b0f2a9 feat: |UI| add simple index (#684) 2025-06-28 15:52:19 +08:00
Dream Hunter
9f535a0a90 feature: update dependencies (#682) 2025-06-24 18:27:45 +08:00
19 changed files with 2132 additions and 1781 deletions

View File

@@ -1,6 +1,11 @@
<!-- markdownlint-disable-file MD004 MD024 MD034 MD036 -->
# CHANGE LOG
## main(v1.0.1)
- feat: |UI| 增加极简模式主页, 可在 `外观` 中切换
- fix: 修复 oauth2 登录时default role 不生效的问题
## v1.0.0
- fix: |UI| 修复 User 查看收件箱,不选择地址时,关键词查询不生效

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"type": "module",
"scripts": {
@@ -28,11 +28,11 @@
"jszip": "^3.10.1",
"mail-parser-wasm": "^0.2.1",
"naive-ui": "^2.42.0",
"postal-mime": "^2.4.3",
"postal-mime": "^2.4.4",
"vooks": "^0.2.12",
"vue": "^3.5.17",
"vue-clipboard3": "^2.0.0",
"vue-i18n": "^11.1.6",
"vue-i18n": "^11.1.10",
"vue-router": "^4.5.1"
},
"devDependencies": {
@@ -40,14 +40,14 @@
"@vicons/material": "^0.13.0",
"@vitejs/plugin-vue": "^5.2.4",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.7.0",
"unplugin-vue-components": "^28.8.0",
"vite": "^6.3.5",
"vite-plugin-pwa": "^1.0.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-plugin-wasm": "^3.4.1",
"vite-plugin-pwa": "^1.0.1",
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0",
"workbox-build": "^7.3.0",
"workbox-window": "^7.3.0",
"wrangler": "^4.20.4"
"wrangler": "^4.25.0"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}

1635
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ export const useGlobalState = createGlobalState(
const toggleDark = useToggle(isDark)
const loading = ref(false);
const announcement = useLocalStorage('announcement', '');
const useSimpleIndex = useLocalStorage('useSimpleIndex', false);
const openSettings = ref({
fetched: false,
title: '',
@@ -142,6 +143,7 @@ export const useGlobalState = createGlobalState(
showAdminPage,
userOauth2SessionState,
userOauth2SessionClientID,
useSimpleIndex,
}
},
)

View File

@@ -11,11 +11,14 @@ import MailBox from '../components/MailBox.vue';
import SendBox from '../components/SendBox.vue';
import AutoReply from './index/AutoReply.vue';
import AccountSettings from './index/AccountSettings.vue';
import Appearance from './common/Appearance.vue';
import Webhook from './index/Webhook.vue';
import Attachment from './index/Attachment.vue';
import About from './common/About.vue';
const { loading, settings, openSettings, indexTab, globalTabplacement } = useGlobalState()
import SimpleIndex from './index/SimpleIndex.vue';
const { loading, settings, openSettings, indexTab, globalTabplacement, useSimpleIndex } = useGlobalState()
const message = useMessage()
const route = useRoute()
@@ -33,6 +36,7 @@ const { t } = useI18n({
sendmail: 'Send Mail',
auto_reply: 'Auto Reply',
accountSettings: 'Account Settings',
appearance: 'Appearance',
about: 'About',
s3Attachment: 'S3 Attachment',
saveToS3Success: 'save to s3 success',
@@ -44,7 +48,8 @@ const { t } = useI18n({
sendbox: '发件箱',
sendmail: '发送邮件',
auto_reply: '自动回复',
accountSettings: '账户设置',
accountSettings: '账户',
appearance: '外观',
about: '关于',
s3Attachment: 'S3附件',
saveToS3Success: '保存到s3成功',
@@ -122,43 +127,51 @@ onMounted(() => {
<template>
<div>
<AddressBar />
<n-tabs v-if="settings.address" type="card" v-model:value="indexTab" :placement="globalTabplacement">
<n-tab-pane name="mailbox" :tab="t('mailbox')">
<div v-if="showMailIdQuery" style="margin-bottom: 10px;">
<n-input-group>
<n-input v-model:value="mailIdQuery" />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
</div>
<MailBox :key="mailBoxKey" :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled"
:saveToS3="saveToS3" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
</n-tab-pane>
<n-tab-pane name="sendbox" :tab="t('sendbox')">
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:deleteMail="deleteSenboxMail" />
</n-tab-pane>
<n-tab-pane name="sendmail" :tab="t('sendmail')">
<SendMail />
</n-tab-pane>
<n-tab-pane name="accountSettings" :tab="t('accountSettings')">
<AccountSettings />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableAutoReply" name="auto_reply" :tab="t('auto_reply')">
<AutoReply />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableWebhook" name="webhook" :tab="t('webhookSettings')">
<Webhook />
</n-tab-pane>
<n-tab-pane v-if="openSettings.isS3Enabled" name="s3_attachment" :tab="t('s3Attachment')">
<Attachment />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableIndexAbout" name="about" :tab="t('about')">
<About />
</n-tab-pane>
</n-tabs>
<div v-if="useSimpleIndex">
<SimpleIndex />
</div>
<div v-else>
<AddressBar />
<n-tabs v-if="settings.address" type="card" v-model:value="indexTab" :placement="globalTabplacement">
<n-tab-pane name="mailbox" :tab="t('mailbox')">
<div v-if="showMailIdQuery" style="margin-bottom: 10px;">
<n-input-group>
<n-input v-model:value="mailIdQuery" />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
</div>
<MailBox :key="mailBoxKey" :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled"
:saveToS3="saveToS3" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
</n-tab-pane>
<n-tab-pane name="sendbox" :tab="t('sendbox')">
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:deleteMail="deleteSenboxMail" />
</n-tab-pane>
<n-tab-pane name="sendmail" :tab="t('sendmail')">
<SendMail />
</n-tab-pane>
<n-tab-pane name="accountSettings" :tab="t('accountSettings')">
<AccountSettings />
</n-tab-pane>
<n-tab-pane name="appearance" :tab="t('appearance')">
<Appearance :showUseSimpleIndex="true" />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableAutoReply" name="auto_reply" :tab="t('auto_reply')">
<AutoReply />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableWebhook" name="webhook" :tab="t('webhookSettings')">
<Webhook />
</n-tab-pane>
<n-tab-pane v-if="openSettings.isS3Enabled" name="s3_attachment" :tab="t('s3Attachment')">
<Attachment />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableIndexAbout" name="about" :tab="t('about')">
<About />
</n-tab-pane>
</n-tabs>
</div>
</div>
</template>

View File

@@ -27,7 +27,7 @@ const { t } = useI18n({
addressCredentialTip: 'Please copy the Mail Address Credential and you can use it to login to your email account.',
delete: 'Delete',
deleteTip: 'Are you sure to delete this email?',
delteAccount: 'Delete Account',
deleteAccount: 'Delete Account',
viewMails: 'View Mails',
viewSendBox: 'View SendBox',
itemCount: 'itemCount',
@@ -46,7 +46,7 @@ const { t } = useI18n({
addressCredentialTip: '请复制邮箱地址凭证,你可以使用它登录你的邮箱。',
delete: '删除',
deleteTip: '确定要删除这个邮箱吗?',
delteAccount: '删除邮箱',
deleteAccount: '删除邮箱',
viewMails: '查看邮件',
viewSendBox: '查看发件箱',
itemCount: '总数',
@@ -273,11 +273,11 @@ onMounted(async () => {
<template #action>
</template>
</n-modal>
<n-modal v-model:show="showDeleteAccount" preset="dialog" :title="t('delteAccount')">
<n-modal v-model:show="showDeleteAccount" preset="dialog" :title="t('deleteAccount')">
<p>{{ t('deleteTip') }}</p>
<template #action>
<n-button :loading="loading" @click="deleteEmail" size="small" tertiary type="error">
{{ t('delteAccount') }}
{{ t('deleteAccount') }}
</n-button>
</template>
</n-modal>

View File

@@ -3,16 +3,23 @@ import { useI18n } from 'vue-i18n'
import { useIsMobile } from '../../utils/composables'
import { useGlobalState } from '../../store'
const props = defineProps({
showUseSimpleIndex: {
type: Boolean,
default: false
}
})
const {
mailboxSplitSize, useIframeShowMail, preferShowTextMail, configAutoRefreshInterval,
globalTabplacement, useSideMargin, useUTCDate
globalTabplacement, useSideMargin, useUTCDate, useSimpleIndex
} = useGlobalState()
const isMobile = useIsMobile()
const { t } = useI18n({
messages: {
en: {
useSimpleIndex: 'Use Simple Index',
mailboxSplitSize: 'Mailbox Split Size',
useIframeShowMail: 'Use iframe Show HTML Mail',
preferShowTextMail: 'Display text Mail by default',
@@ -26,6 +33,7 @@ const { t } = useI18n({
autoRefreshInterval: 'Auto Refresh Interval(Sec)',
},
zh: {
useSimpleIndex: '使用极简主页',
mailboxSplitSize: '邮箱界面分栏大小',
preferShowTextMail: '默认以文本显示邮件',
useIframeShowMail: '使用iframe显示HTML邮件',
@@ -57,6 +65,9 @@ const { t } = useI18n({
60: '60', 120: '120', 180: '180', 240: '240'
}" />
</n-form-item-row>
<n-form-item-row v-if="props.showUseSimpleIndex" :label="t('useSimpleIndex')">
<n-switch v-model:value="useSimpleIndex" :round="false" />
</n-form-item-row>
<n-form-item-row :label="t('preferShowTextMail')">
<n-switch v-model:value="preferShowTextMail" :round="false" />
</n-form-item-row>

View File

@@ -5,7 +5,6 @@ import { useRouter } from 'vue-router'
import { useGlobalState } from '../../store'
import { api } from '../../api'
import Appearance from '../common/Appearance.vue'
import { getRouterPathWithLang } from '../../utils'
const {
@@ -15,24 +14,24 @@ const router = useRouter()
const message = useMessage()
const showLogout = ref(false)
const showDelteAccount = ref(false)
const showDeleteAccount = ref(false)
const { locale, t } = useI18n({
messages: {
en: {
logout: "Logout",
delteAccount: "Delete Account",
deleteAccount: "Delete Account",
showAddressCredential: 'Show Address Credential',
logoutConfirm: 'Are you sure to logout?',
delteAccount: "Delete Account",
delteAccountConfirm: "Are you sure to delete your account and all emails for this account?",
deleteAccount: "Delete Account",
deleteAccountConfirm: "Are you sure to delete your account and all emails for this account?",
},
zh: {
logout: '退出登录',
delteAccount: "删除账户",
deleteAccount: "删除账户",
showAddressCredential: '查看邮箱地址凭证',
logoutConfirm: '确定要退出登录吗?',
delteAccount: "删除账户",
delteAccountConfirm: "确定要删除你的账户和其中的所有邮件吗?",
deleteAccount: "删除账户",
deleteAccountConfirm: "确定要删除你的账户和其中的所有邮件吗?",
}
}
});
@@ -60,15 +59,14 @@ const deleteAccount = async () => {
<template>
<div class="center" v-if="settings.address">
<n-card :bordered="false" embedded>
<Appearance />
<n-button @click="showAddressCredential = true" type="primary" secondary block strong>
{{ t('showAddressCredential') }}
</n-button>
<n-button @click="showLogout = true" secondary block strong>
{{ t('logout') }}
</n-button>
<n-button @click="showDelteAccount = true" type="error" secondary block strong>
{{ t('delteAccount') }}
<n-button @click="showDeleteAccount = true" type="error" secondary block strong>
{{ t('deleteAccount') }}
</n-button>
</n-card>
<n-modal v-model:show="showLogout" preset="dialog" :title="t('logout')">
@@ -79,11 +77,11 @@ const deleteAccount = async () => {
</n-button>
</template>
</n-modal>
<n-modal v-model:show="showDelteAccount" preset="dialog" :title="t('delteAccount')">
<p>{{ t('delteAccountConfirm') }}</p>
<n-modal v-model:show="showDeleteAccount" preset="dialog" :title="t('deleteAccount')">
<p>{{ t('deleteAccountConfirm') }}</p>
<template #action>
<n-button :loading="loading" @click="deleteAccount" size="small" tertiary type="error">
{{ t('delteAccount') }}
{{ t('deleteAccount') }}
</n-button>
</template>
</n-modal>

View File

@@ -0,0 +1,252 @@
<script setup>
import { ref, onMounted, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useMessage } from 'naive-ui'
import {
ExitToAppFilled,
ContentCopyFilled,
RefreshFilled,
ArrowBackIosNewFilled,
ArrowForwardIosFilled,
SettingsFilled
} from '@vicons/material'
import { useGlobalState } from '../../store'
import { api } from '../../api'
import Login from '../common/Login.vue'
import AccountSettings from './AccountSettings.vue'
import { processItem } from '../../utils/email-parser'
import { utcToLocalDate } from '../../utils'
import ShadowHtmlComponent from '../../components/ShadowHtmlComponent.vue'
const { jwt, settings, useSimpleIndex, useUTCDate, showAddressCredential } = useGlobalState()
const message = useMessage()
// 邮件数据
const currentPage = ref(1)
const totalCount = ref(0)
const loading = ref(false)
const currentMail = ref(null)
const showAccountSettingsCard = ref(false)
const { t } = useI18n({
messages: {
en: {
exitSimpleIndex: 'Exit Simple',
copyAddress: 'Copy',
addressCopied: 'Address copied successfully',
refreshMails: 'Refresh',
noMails: 'No mails found',
prevPage: 'Previous',
nextPage: 'Next',
refreshSuccess: 'Mails refreshed successfully',
mailCount: '{current} / {total} emails',
accountSettings: "Account Settings",
addressCredential: 'Mail Address Credential',
addressCredentialTip: 'Please copy the Mail Address Credential and you can use it to login',
},
zh: {
exitSimpleIndex: '退出极简',
copyAddress: '复制',
addressCopied: '地址复制成功',
refreshMails: '刷新',
noMails: '暂无邮件',
prevPage: '上一页',
nextPage: '下一页',
refreshSuccess: '邮件刷新成功',
mailCount: '{current} / {total} 封邮件',
accountSettings: "账户设置",
addressCredential: '邮箱地址凭证',
addressCredentialTip: '请复制邮箱地址凭证,你可以使用它登录你的邮箱。'
}
}
})
// 复制地址
const copyAddress = async () => {
try {
await navigator.clipboard.writeText(settings.value.address)
message.success(t('addressCopied'))
} catch (error) {
message.error('复制失败')
}
}
// 获取邮件数据
const fetchMails = async () => {
if (!settings.value.address) return
try {
const { results, count } = await api.fetch(`/api/mails?limit=1&offset=${currentPage.value - 1}`)
totalCount.value = count > 0 ? count : totalCount.value;
const rawMail = results && results.length > 0 ? results[0] : null
currentMail.value = rawMail ? await processItem(rawMail) : null
} catch (error) {
console.error('Failed to fetch mails:', error)
message.error('获取邮件失败')
}
}
// 刷新邮件
const refreshMails = async () => {
currentPage.value = 1
await fetchMails()
message.success(t('refreshSuccess'))
}
// 分页控制
const currentPageDisplay = computed(() => currentPage.value)
const totalPages = computed(() => Math.max(1, totalCount.value))
const canGoPrev = computed(() => currentPage.value > 1)
const canGoNext = computed(() => currentPage.value < totalPages.value)
const prevPage = async () => {
if (canGoPrev.value) {
currentPage.value--
}
}
const nextPage = async () => {
if (canGoNext.value) {
currentPage.value++
}
}
// 监听页面变化
watch(currentPage, () => {
fetchMails()
})
onMounted(async () => {
await api.getSettings()
await fetchMails()
})
</script>
<template>
<div class="center">
<div v-if="!settings.address">
<n-card :bordered="false" embedded>
<Login />
</n-card>
</div>
<div v-else>
<n-card :bordered="false" embedded>
<div style="text-align: center; margin-bottom: 16px; font-size: 18px;">
<n-text strong size="large">{{ settings.address }}</n-text>
</div>
<n-flex justify="center">
<n-button @click="refreshMails" :loading="loading" type="primary" tertiary size="small">
<template #icon>
<n-icon>
<RefreshFilled />
</n-icon>
</template>
{{ t('refreshMails') }}
</n-button>
<n-button @click="copyAddress" tertiary size="small">
<template #icon>
<n-icon>
<ContentCopyFilled />
</n-icon>
</template>
{{ t('copyAddress') }}
</n-button>
<n-button @click="useSimpleIndex = false" tertiary size="small">
<template #icon>
<n-icon>
<ExitToAppFilled />
</n-icon>
</template>
{{ t('exitSimpleIndex') }}
</n-button>
<n-button @click="showAccountSettingsCard = true" tertiary size="small">
<template #icon>
<n-icon>
<SettingsFilled />
</n-icon>
</template>
{{ t('accountSettings') }}
</n-button>
</n-flex>
</n-card>
<!-- 账户设置卡片 -->
<n-card v-if="showAccountSettingsCard" :bordered="false" embedded closable
@close="showAccountSettingsCard = false" :title="t('accountSettings')">
<AccountSettings />
</n-card>
<n-card :bordered="false" embedded style="text-align: left;">
<div v-if="totalCount > 1">
<n-flex justify="space-between">
<n-button @click="prevPage" :disabled="!canGoPrev" text size="small">
<template #icon>
<n-icon>
<ArrowBackIosNewFilled />
</n-icon>
</template>
{{ t('prevPage') }}
</n-button>
<n-text size="small">
{{ t('mailCount', { current: currentPageDisplay, total: totalCount }) }}
</n-text>
<n-button @click="nextPage" :disabled="!canGoNext" text size="small" icon-placement="right">
<template #icon>
<n-icon>
<ArrowForwardIosFilled />
</n-icon>
</template>
{{ t('nextPage') }}
</n-button>
</n-flex>
</div>
<div v-if="!currentMail" class="no-mail">
<n-empty :description="t('noMails')" />
</div>
<div v-else>
<h3 v-if="currentMail.subject">{{ currentMail.subject }}</h3>
<n-space>
<n-tag type="info">
ID: {{ currentMail.id }}
</n-tag>
<n-tag type="info">
{{ utcToLocalDate(currentMail.created_at, useUTCDate.value) }}
</n-tag>
<n-tag type="info">
FROM: {{ currentMail.source }}
</n-tag>
</n-space>
<div style="margin-top: 16px;">
<ShadowHtmlComponent v-if="currentMail.message" :htmlContent="currentMail.message" />
<pre v-else>{{ currentMail.text }}</pre>
</div>
</div>
</n-card>
</div>
<n-modal v-model:show="showAddressCredential" preset="dialog" :title="t('addressCredential')">
<span>
<p>{{ t("addressCredentialTip") }}</p>
</span>
<n-card embedded>
<b>{{ jwt }}</b>
</n-card>
</n-modal>
</div>
</template>
<style scoped>
.center {
max-width: 800px;
margin: 0 auto;
}
.n-card {
margin-top: 20px;
width: 100%;
}
</style>

View File

@@ -1,6 +1,6 @@
{
"name": "temp-email-pages",
"version": "1.0.0",
"version": "1.0.1",
"description": "",
"main": "index.js",
"scripts": {
@@ -11,7 +11,7 @@
"author": "",
"license": "ISC",
"devDependencies": {
"wrangler": "^4.20.4"
"wrangler": "^4.25.0"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}

View File

@@ -1,12 +1,12 @@
{
"name": "temp-mail-docs",
"private": true,
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"devDependencies": {
"@types/node": "^24.0.3",
"@types/node": "^24.0.15",
"vitepress": "^1.6.3",
"wrangler": "^4.20.4"
"wrangler": "^4.25.0"
},
"scripts": {
"dev": "vitepress dev docs",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"type": "module",
"scripts": {
@@ -11,24 +11,24 @@
"build": "wrangler deploy --dry-run --outdir dist --minify"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0",
"@cloudflare/workers-types": "^4.20250719.0",
"@eslint/js": "9.18.0",
"@simplewebauthn/types": "10.0.0",
"@types/node": "^22.15.32",
"@types/node": "^22.16.5",
"eslint": "9.18.0",
"globals": "^15.15.0",
"typescript-eslint": "^8.34.1",
"wrangler": "^4.20.4"
"typescript-eslint": "^8.37.0",
"wrangler": "^4.25.0"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.832.0",
"@aws-sdk/s3-request-presigner": "^3.832.0",
"@aws-sdk/client-s3": "^3.848.0",
"@aws-sdk/s3-request-presigner": "^3.848.0",
"@simplewebauthn/server": "10.0.1",
"hono": "^4.8.1",
"hono": "^4.8.5",
"jsonpath-plus": "^10.3.0",
"mimetext": "^3.0.27",
"postal-mime": "^2.4.3",
"resend": "^4.6.0",
"postal-mime": "^2.4.4",
"resend": "^4.7.0",
"telegraf": "4.16.3",
"worker-mailer": "^1.1.4"
},

1079
worker/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,23 @@ const getNameRegex = (c: Context<HonoCustomType>): RegExp => {
return DEFAULT_NAME_REGEX;
}
export async function updateAddressUpdatedAt(
c: Context<HonoCustomType>,
address: string | undefined | null
): Promise<void> {
if (!address) {
return;
}
// update address updated_at
try {
await c.env.DB.prepare(
`UPDATE address SET updated_at = datetime('now') where name = ?`
).bind(address).run();
} catch (e) {
console.warn("Failed to update address updated_at", e);
}
}
export const newAddress = async (
c: Context<HonoCustomType>,
{
@@ -108,6 +125,7 @@ export const newAddress = async (
if (!success) {
throw new Error("Failed to create address")
}
await updateAddressUpdatedAt(c, name);
} catch (e) {
const message = (e as Error).message;
if (message && message.includes("UNIQUE")) {

View File

@@ -1,5 +1,5 @@
export const CONSTANTS = {
VERSION: 'v' + '1.0.0',
VERSION: 'v' + '1.0.1',
// DB Version
DB_VERSION_KEY: 'db_version',

View File

@@ -2,7 +2,7 @@ import { Context, Hono } from 'hono'
import i18n from '../i18n';
import { getBooleanValue, getJsonSetting, checkCfTurnstile, getStringValue, getSplitStringListValue } from '../utils';
import { newAddress, handleListQuery, deleteAddressWithData, getAddressPrefix, getAllowDomains } from '../common'
import { newAddress, handleListQuery, deleteAddressWithData, getAddressPrefix, getAllowDomains, updateAddressUpdatedAt } from '../common'
import { CONSTANTS } from '../constants'
import auto_reply from './auto_reply'
import webhook_settings from './webhook_settings';
@@ -20,27 +20,6 @@ api.post('/api/attachment/delete', s3_attachment.deleteKey)
api.post('/api/attachment/put_url', s3_attachment.getSignedPutUrl)
api.post('/api/attachment/get_url', s3_attachment.getSignedGetUrl)
export async function updateAddressUpdatedAt(
c: Context<HonoCustomType>,
address: string | undefined | null
): Promise<void> {
if (!address) {
return;
}
// update address updated_at
try {
if (address) {
await c.env.DB.prepare(
`UPDATE address SET updated_at = datetime('now') where name = ?`
).bind(address).run();
}
} catch (e) {
console.warn("Failed to update address updated_at")
}
}
api.get('/api/mails', async (c) => {
const { address } = c.get("jwtPayload")
if (!address) {

View File

@@ -6,6 +6,7 @@ import { getJsonSetting } from "../utils"
import { CONSTANTS } from "../constants";
import { unbindTelegramByAddress } from '../telegram_api/common';
import i18n from '../i18n';
import { updateAddressUpdatedAt } from '../common';
const UserBindAddressModule = {
bind: async (c: Context<HonoCustomType>) => {
@@ -237,6 +238,7 @@ const UserBindAddressModule = {
if (!newAddressSuccess) {
throw new Error("Failed to create address")
}
await updateAddressUpdatedAt(c, address);
// find new address id
const new_address_id = await c.env.DB.prepare(
`SELECT id FROM address WHERE name = ?`

View File

@@ -2,7 +2,7 @@ import { Context } from 'hono';
import { Jwt } from 'hono/utils/jwt'
import i18n from '../i18n';
import { getJsonSetting } from '../utils';
import { getJsonSetting, getStringValue, getUserRoles } from '../utils';
import { UserOauth2Settings } from '../models';
import { CONSTANTS } from '../constants';
@@ -110,6 +110,21 @@ export default {
if (!user_id) {
return c.text(msgs.UserNotFoundMsg, 400)
}
const defaultRole = getStringValue(c.env.USER_DEFAULT_ROLE);
if (!defaultRole) return c.json({ success: true })
const user_roles = getUserRoles(c);
if (!user_roles.find((r) => r.role === defaultRole)) {
return c.text(msgs.InvalidUserDefaultRoleMsg, 500);
}
// update user roles
const { success: success2 } = await c.env.DB.prepare(
`INSERT INTO user_roles (user_id, role_text)`
+ ` VALUES (?, ?)`
+ ` ON CONFLICT(user_id) DO NOTHING`
).bind(user_id, defaultRole).run();
if (!success2) {
return c.text(msgs.FailedUpdateUserDefaultRoleMsg, 500);
}
// create jwt
const jwt = await Jwt.sign({
user_email: email,