新增中英文切换

This commit is contained in:
eoao
2025-07-22 22:02:23 +08:00
parent 249edb9e19
commit bbcff4908d
64 changed files with 1951 additions and 1298 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,8 +6,8 @@
<title></title>
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<script type="module" crossorigin src="/assets/index-DlA47rIk.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BkPsvh6f.css">
<script type="module" crossorigin src="/assets/index-Cjzn1ZdJ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dx0iKe9o.css">
</head>
<body>
<div id="loading-first">

File diff suppressed because it is too large Load Diff

View File

@@ -14,10 +14,11 @@
"wrangler": "^4.7.0"
},
"dependencies": {
"@cloudflare/vite-plugin": "^1.0.5",
"@cloudflare/vite-plugin": "1.6.0",
"dayjs": "^1.11.13",
"drizzle-orm": "^0.42.0",
"hono": "^4.7.5",
"i18next": "^25.3.2",
"linkedom": "^0.18.10",
"postal-mime": "^2.4.3",
"resend": "^4.5.1",

View File

@@ -2,12 +2,12 @@ import app from '../hono/hono';
import emailService from '../service/email-service';
import result from '../model/result';
app.get('/sysEmail/list',async (c) => {
app.get('/allEmail/list',async (c) => {
const data = await emailService.allList(c, c.req.query());
return c.json(result.ok(data));
})
app.delete('/sysEmail/delete',async (c) => {
app.delete('/allEmail/delete',async (c) => {
const list = await emailService.physicsDelete(c, c.req.query());
return c.json(result.ok(list));
})

View File

@@ -3,6 +3,6 @@ import analysisService from '../service/analysis-service';
import result from '../model/result';
app.get('/analysis/echarts', async (c) => {
const data = await analysisService.echarts(c);
const data = await analysisService.echarts(c, c.req.query());
return c.json(result.ok(data));
})

View File

@@ -6,7 +6,7 @@ const constant = {
ATTACHMENT_PREFIX: 'attachments/',
BACKGROUND_PREFIX: 'static/background/',
ADMIN_ROLE: {
name: '超级管理员',
name: 'admin',
sendCount: 0,
sendType: 'count',
accountCount: 0

View File

@@ -46,53 +46,53 @@ const analysisDao = {
return results[0];
},
async userDayCount(c) {
async userDayCount(c, diffHours) {
const { results } = await c.env.db.prepare(`
SELECT
DATE(create_time,'+8 hours') AS date,
DATE(create_time,'+${diffHours} hours') AS date,
COUNT(*) AS total
FROM
user
WHERE
DATE(create_time,'+8 hours') BETWEEN DATE('now', '-15 days', '+8 hours') AND DATE('now','-1 day','+8 hours')
DATE(create_time,'+${diffHours} hours') BETWEEN DATE('now', '-15 days', '+${diffHours} hours') AND DATE('now','-1 day','+${diffHours} hours')
GROUP BY
DATE(create_time,'+8 hours')
DATE(create_time,'+${diffHours} hours')
ORDER BY
date ASC
`).all();
return results;
},
async receiveDayCount(c) {
async receiveDayCount(c, diffHours) {
const { results } = await c.env.db.prepare(`
SELECT
DATE(create_time,'+8 hours') AS date,
DATE(create_time,'+${diffHours} hours') AS date,
COUNT(*) AS total
FROM
email
WHERE
DATE(create_time,'+8 hours') BETWEEN DATE('now', '-15 days', '+8 hours') AND DATE('now','-1 day','+8 hours')
DATE(create_time,'+${diffHours} hours') BETWEEN DATE('now', '-15 days', '+${diffHours} hours') AND DATE('now','-1 day','+${diffHours} hours')
AND type = 0
GROUP BY
DATE(create_time,'+8 hours')
DATE(create_time,'+${diffHours} hours')
ORDER BY
date ASC
`).all();
return results;
},
async sendDayCount(c) {
async sendDayCount(c, diffHours) {
const { results } = await c.env.db.prepare(`
SELECT
DATE(create_time,'+8 hours') AS date,
DATE(create_time,'+${diffHours} hours') AS date,
COUNT(*) AS total
FROM
email
WHERE
DATE(create_time,'+8 hours') BETWEEN DATE('now', '-15 days', '+8 hours') AND DATE('now','-1 day','+8 hours')
DATE(create_time,'+${diffHours} hours') BETWEEN DATE('now', '-15 days', '+${diffHours} hours') AND DATE('now','-1 day','+${diffHours} hours')
AND type = 1
GROUP BY
DATE(create_time,'+8 hours')
DATE(create_time,'+${diffHours} hours')
ORDER BY
date ASC
`).all();

View File

@@ -66,8 +66,8 @@ export async function email(message, env, ctx) {
if (banEmailType === roleConst.banEmailType.ALL) return
if (banEmailType === roleConst.banEmailType.CONTENT) {
email.html = '邮件内容已被移除'
email.text = '邮件内容已被移除'
email.html = 'removed'
email.text = 'removed'
email.attachments = []
}
@@ -80,8 +80,8 @@ export async function email(message, env, ctx) {
if (banEmailType === roleConst.banEmailType.ALL) return
if (banEmailType === roleConst.banEmailType.CONTENT) {
email.html = '邮件内容已被移除'
email.text = '邮件内容已被移除'
email.html = 'removed'
email.text = 'removed'
email.attachments = []
}
@@ -115,11 +115,6 @@ export async function email(message, env, ctx) {
status: emailConst.status.SAVING
};
let headers = message.headers
console.log(headers.get('X-Cf-Spamh-Score'))
console.log(email)
const attachments = [];
const cidAttachments = [];

View File

@@ -13,7 +13,7 @@ import '../api/resend-api';
import '../api/user-api';
import '../api/my-api';
import '../api/role-api'
import '../api/sys-email-api'
import '../api/all-email-api'
import '../api/init-api'
import '../api/analysis-api'
import '../api/reg-key-api'

View File

@@ -0,0 +1,93 @@
const en = {
IncorrectPwd: 'Incorrect password',
addAccountDisabled: 'Add email account function is disabled',
regDisabled: 'Sign up is disabled',
emptyEmail: 'Email cannot be empty',
notEmail: 'Invalid email',
notExistDomain: 'Email domain does not exist',
isDelAccount: 'This Email has been deleted',
isRegAccount: 'This Email is already signed up',
accountLimit: 'Email account limit reached',
delMyAccount: 'Cannot delete your own email account',
noUserAccount: 'This email does not belong to the current user',
usernameLengthLimit: 'Username length exceeds the limit',
noOsDomainSendPic: 'Cannot send body images: R2 domain not configured',
noOsSendPic: 'Cannot send body images: R2 object storage not configured',
noOsDomainSendAtt: 'Cannot send attachments: R2 domain not configured',
noOsSendAtt: 'Cannot send attachments: R2 object storage not configured',
disabledSend: 'Email sending is disabled',
noSeparateSend: 'Attachments are not supported in separate sending',
userNoSendTotal: 'User has no remaining sends',
daySendLimit: 'Daily send limit reached',
totalSendLimit: 'Total send limit reached',
daySendLack: 'Not enough remaining sends today',
totalSendLack: 'Not enough total remaining sends',
senderAccountNotExist: 'Sender email does not exist',
noResendToken: 'Resend API token not configured',
sendEmailNotCurUser: 'Sender email does not belong to current user',
notExistEmailReply: 'Mail does not exist and cannot be replied to',
pwdLengthLimit: 'Password length exceeds the limit',
emailLengthLimit: 'Email length exceeds the limit',
pwdMinLengthLimit: 'Password must be at least 6 characters',
notEmailDomain: 'Invalid email domain',
emptyRegKey: 'Invite code cannot be empty',
notExistRegKey: 'Invite code does not exist',
noRegKeyTotal: 'Invite code usage limit reached',
regKeyExpire: 'Invite code has expired',
emailAndPwdEmpty: 'Email and password cannot be empty',
notExistUser: 'Email does not exist',
isDelUser: 'This email has been deleted',
isBanUser: 'This email has been banned',
regKeyUseCount: 'Usage count cannot be empty',
emptyRegKeyExpire: 'Valid until time cannot be empty',
isExistRegKye: 'Invite code already exists',
roleNotExist: 'Role does not exist',
emptyRoleName: 'Role name cannot be empty',
roleNameExist: 'Role name already exists',
delDefRole: 'Default role cannot be deleted',
notJsonDomain: 'Environment variable "domain" must be in JSON format',
noOsUpBack: 'Cannot upload background: R2 object storage not configured',
noOsDomainUpBack: 'Cannot upload background: R2 domain not configured',
starNotExistEmail: 'Starred email does not exist',
emptyBotToken: 'Verification token cannot be empty',
botVerifyFail: 'Bot verification failed, please try again',
authExpired: 'Authentication expired, please log in again',
unauthorized: 'Unauthorized',
perms: {
"邮件": "Email",
"邮件发送": "Send email",
"邮件删除": "Delete email",
"邮箱侧栏": "Account",
"邮箱查看": "View account",
"邮箱添加": "Add account",
"邮箱删除": "Delete account",
"个人设置": "Settings",
"用户注销": "Delete user",
"分析页": "Analytics",
"数据查看": "View data",
"用户信息": "All users",
"用户查看": "View user",
"用户添加": "Add user",
"密码修改": "Change password",
"状态修改": "Change status",
"权限修改": "Change role",
"用户删除": "Delete user",
"邮件列表": "All mail",
"邮件查看": "View email",
"权限控制": "Role",
"身份查看": "View role",
"身份修改": "Change role",
"身份删除": "Delete role",
"注册密钥": "Invite code",
"密钥查看": "View code",
"密钥添加": "Add code",
"密钥删除": "Delete code",
"系统设置": "System settings",
"设置查看": "View settings",
"设置修改": "Change settings",
"物理清空": "Physical purge",
"发件重置": "Reset send count"
}
};
export default en;

View File

@@ -0,0 +1,30 @@
import i18next from 'i18next';
import zh from './zh.js'
import en from './en.js'
import app from '../hono/hono';
app.use('*', async (c, next) => {
const lang = c.req.header('accept-language').split('-')[0]
i18next.init({
lng: lang,
});
return await next()
})
const resources = {
en: {
translation: en
},
zh: {
translation: zh,
},
};
i18next.init({
fallbackLng: 'en',
resources,
});
export const t = (key) => i18next.t(key)
export default i18next;

View File

@@ -0,0 +1,93 @@
const zh = {
IncorrectPwd: '密码输入错误',
addAccountDisabled: '添加邮箱功能已关闭',
regDisabled: '注册功能已关闭',
emptyEmail: '邮箱不能为空',
notEmail: '非法邮箱',
notExistDomain: '不存在的邮箱域名',
isDelAccount: '该邮箱已被注销',
isRegAccount: '该邮箱已被注册',
accountLimit: '添加邮箱数量到达限制',
delMyAccount: '不可以删除自己的邮箱',
noUserAccount: '该邮箱不属于当前用户',
usernameLengthLimit: '用户名长度超出限制',
noOsDomainSendPic: 'r2域名未配置不能发送正文图片',
noOsSendPic: 'r2对象存储未配置不能发送正文图片',
noOsDomainSendAtt: 'r2域名未配置不能发送附件',
noOsSendAtt: 'r2对象存储未配置不能发送附件',
disabledSend: '邮件发送功能已停用',
noSeparateSend: '分别发送暂时不支持附件',
userNoSendTotal: '用户无发送次数',
daySendLimit: '发送次数已到达每日限制',
totalSendLimit: '发送次数已到达限制',
daySendLack: '当日剩余发送次数不足',
totalSendLack: '剩余发送次数不足',
senderAccountNotExist: '发件人邮箱不存在',
noResendToken: 'resend密钥未配置',
sendEmailNotCurUser: '发件人邮箱非当前用户所有',
notExistEmailReply: '邮件不存在无法回复',
pwdLengthLimit: '密码长度超出限制',
emailLengthLimit: '邮箱长度超出限制',
pwdMinLengthLimit: '密码不能小于6位',
notEmailDomain: '非法邮箱域名',
emptyRegKey: '注册码不能为空',
notExistRegKey: '注册码不存在',
noRegKeyTotal: '注册码使用次数已耗尽',
regKeyExpire: '注册码已过期',
emailAndPwdEmpty: '邮箱和密码不能为空',
notExistUser: '该邮箱不存在',
isDelUser: '该邮箱已被注销',
isBanUser: '该邮箱已被禁用',
regKeyUseCount: '使用次数不能为空',
emptyRegKeyExpire: '有效时间不能为空',
isExistRegKye: '注册码已存在',
roleNotExist: '权限身份不存在',
emptyRoleName: '身份名不能为空',
roleNameExist: '身份名已存在',
delDefRole: '默认身份不能删除',
notJsonDomain: '环境变量domain必须是JSON类型',
noOsUpBack: 'r2对象存储未配置不能上传背景',
noOsDomainUpBack: 'r2域名未配置不上传背景',
starNotExistEmail: '星标的邮件不存在',
emptyBotToken: '验证token不能为空',
botVerifyFail: '人机验证失败,请重试',
authExpired: '身份认证失效,请重新登录',
unauthorized: '权限不足',
perms: {
"邮件": "邮件",
"邮件发送": "邮件发送",
"邮件删除": "邮件删除",
"邮箱侧栏": "邮箱侧栏",
"邮箱查看": "邮箱查看",
"邮箱添加": "邮箱添加",
"邮箱删除": "邮箱删除",
"个人设置": "个人设置",
"用户注销": "用户注销",
"分析页": "分析页",
"数据查看": "数据查看",
"用户信息": "用户信息",
"用户查看": "用户查看",
"用户添加": "用户添加",
"密码修改": "密码修改",
"状态修改": "状态修改",
"权限修改": "权限修改",
"用户删除": "用户删除",
"邮件列表": "全部邮件",
"邮件查看": "邮件查看",
"权限控制": "权限控制",
"身份查看": "身份查看",
"身份修改": "身份修改",
"身份删除": "身份删除",
"注册密钥": "注册密钥",
"密钥查看": "密钥查看",
"密钥添加": "密钥添加",
"密钥删除": "密钥删除",
"系统设置": "系统设置",
"设置查看": "设置查看",
"设置修改": "设置修改",
"物理清空": "物理清空",
'发件重置': '发件重置'
}
}
export default zh

View File

@@ -16,10 +16,16 @@ const init = {
await this.v1_3DB(c);
await this.v1_3_1DB(c);
await this.v1_4DB(c);
await this.v1_5DB(c);
await settingService.refresh(c);
return c.text('初始化成功');
},
async v1_5DB(c) {
await c.env.db.prepare(`UPDATE perm SET perm_key = 'sys-email:list' WHERE perm_key = 'all-email:list'`).run();
await c.env.db.prepare(`UPDATE perm SET perm_key = 'sys-email:delete' WHERE perm_key = 'all-email:delete'`).run();
},
async v1_4DB(c) {
await c.env.db.prepare(`
CREATE TABLE IF NOT EXISTS reg_key (

View File

@@ -5,6 +5,7 @@ import KvConst from '../const/kv-const';
import dayjs from 'dayjs';
import userService from '../service/user-service';
import permService from '../service/perm-service';
import { t } from '../i18n/i18n'
import app from '../hono/hono';
const exclude = [
@@ -29,8 +30,8 @@ const requirePerms = [
'/role/tree',
'/role/set',
'/role/setDefault',
'/sysEmail/list',
'/sysEmail/delete',
'/allEmail/list',
'/allEmail/delete',
'/setting/physicsDeleteAll',
'/setting/setBackground',
'/setting/set',
@@ -67,8 +68,8 @@ const premKey = {
'user:set-status': ['/user/setStatus'],
'user:set-type': ['/user/setType'],
'user:delete': ['/user/delete'],
'sys-email:query': ['/sysEmail/list'],
'sys-email:delete': ['/sysEmail/delete'],
'all-email:query': ['/allEmail/list'],
'all-email:delete': ['/allEmail/delete'],
'setting:query': ['/setting/query'],
'setting:set': ['/setting/set', '/setting/setBackground'],
'setting:clean': ['/setting/physicsDeleteAll'],
@@ -100,18 +101,18 @@ app.use('*', async (c, next) => {
const result = await jwtUtils.verifyToken(c, jwt);
if (!result) {
throw new BizError('身份认证失效,请重新登录', 401);
throw new BizError(t('authExpired'), 401);
}
const { userId, token } = result;
const authInfo = await c.env.kv.get(KvConst.AUTH_INFO + userId, { type: 'json' });
if (!authInfo) {
throw new BizError('身份认证失效,请重新登录', 401);
throw new BizError(t('authExpired'), 401);
}
if (!authInfo.tokens.includes(token)) {
throw new BizError('身份认证失效,请重新登录', 401);
throw new BizError(t('authExpired'), 401);
}
const permIndex = requirePerms.findIndex(item => {
@@ -129,7 +130,7 @@ app.use('*', async (c, next) => {
});
if (userPermIndex === -1 && authInfo.user.email !== c.env.admin) {
throw new BizError('权限不足', 403);
throw new BizError(t('unauthorized'), 403);
}
}

View File

@@ -10,37 +10,39 @@ import { isDel } from '../const/entity-const';
import settingService from './setting-service';
import turnstileService from './turnstile-service';
import roleService from './role-service';
import { t } from '../i18n/i18n';
const accountService = {
async add(c, params, userId) {
if (!await settingService.isAddEmail(c)) {
throw new BizError('添加邮箱功能已关闭');
throw new BizError(t('addAccountDisabled'));
}
let { email, token } = params;
if (!email) {
throw new BizError('邮箱不能为空');
throw new BizError(t('emptyEmail'));
}
if (!verifyUtils.isEmail(email)) {
throw new BizError('非法邮箱');
throw new BizError(t('notEmail'));
}
if (!c.env.domain.includes(emailUtils.getDomain(email))) {
throw new BizError('不存在的邮箱域名');
throw new BizError(t('notExistDomain'));
}
const accountRow = await this.selectByEmailIncludeDelNoCase(c, email);
if (accountRow && accountRow.isDel === isDel.DELETE) {
throw new BizError('该邮箱已被注销');
throw new BizError(t('isDelAccount'));
}
if (accountRow) {
throw new BizError('该邮箱已被注册');
throw new BizError(t('isRegAccount'));
}
const userRow = await userService.selectById(c, userId);
@@ -48,7 +50,7 @@ const accountService = {
if (roleRow.accountCount && userRow.email !== c.env.admin) {
const userAccountCount = await accountService.countUserAccount(c, userId)
if(userAccountCount >= roleRow.accountCount) throw new BizError(`添加邮箱数量到达限制`, 403);
if(userAccountCount >= roleRow.accountCount) throw new BizError(t('accountLimit'), 403);
}
if (await settingService.isAddEmailVerify(c)) {
@@ -109,11 +111,11 @@ const accountService = {
const accountRow = await this.selectById(c, accountId);
if (accountRow.email === user.email) {
throw new BizError('不可以删除自己的邮箱');
throw new BizError(t('delMyAccount'));
}
if (accountRow.userId !== user.userId) {
throw new BizError('该邮箱不属于当前用户');
throw new BizError(t('noUserAccount'));
}
await orm(c).update(account).set({ isDel: isDel.DELETE }).where(
@@ -182,7 +184,7 @@ const accountService = {
async setName(c, params, userId) {
const { name, accountId } = params
if (name.length > 30) {
throw new BizError('用户名长度超出限制');
throw new BizError(t('usernameLengthLimit'));
}
await orm(c).update(account).set({name}).where(and(eq(account.userId, userId),eq(account.accountId, accountId))).run();
}

View File

@@ -8,7 +8,21 @@ import dayjs from 'dayjs';
import { toUtc } from '../utils/date-uitil';
const analysisService = {
async echarts(c) {
async echarts(c, params) {
const { timeZone } = params;
let utcDate = toUtc().startOf('day');
let localDate = utcDate.tz(timeZone);
utcDate = dayjs(utcDate.format('YYYY-MM-DD HH:mm:ss'))
localDate = dayjs(localDate.format('YYYY-MM-DD HH:mm:ss'))
//获取时差
const diffHours = localDate.diff(utcDate, 'hour',true);
const [
@@ -30,17 +44,17 @@ const analysisService = {
.limit(6),
analysisDao.userDayCount(c),
analysisDao.receiveDayCount(c),
analysisDao.sendDayCount(c),
analysisDao.userDayCount(c, diffHours),
analysisDao.receiveDayCount(c, diffHours),
analysisDao.sendDayCount(c, diffHours),
c.env.kv.get(kvConst.SEND_DAY_COUNT + dayjs().format('YYYY-MM-DD')),
]);
const userDayCount = this.filterEmptyDay(userDayCountRaw);
const receiveDayCount = this.filterEmptyDay(receiveDayCountRaw);
const sendDayCount = this.filterEmptyDay(sendDayCountRaw);
const userDayCount = this.filterEmptyDay(userDayCountRaw, timeZone);
const receiveDayCount = this.filterEmptyDay(receiveDayCountRaw, timeZone);
const sendDayCount = this.filterEmptyDay(sendDayCountRaw, timeZone);
const daySendTotal = daySendTotalRaw || 0;
@@ -58,8 +72,8 @@ const analysisService = {
};
},
filterEmptyDay(data) {
const today = toUtc().tz('Asia/Shanghai').subtract(1, 'day');
filterEmptyDay(data, timeZone) {
const today = toUtc().tz(timeZone).subtract(1, 'day');
const previousDays = Array.from({ length: 15 }, (_, i) => {
return today.subtract(i, 'day').format('YYYY-MM-DD');
}).reverse();

View File

@@ -16,6 +16,7 @@ import user from '../entity/user';
import starService from './star-service';
import dayjs from 'dayjs';
import kvConst from '../const/kv-const';
import { t } from '../i18n/i18n'
const emailService = {
@@ -134,27 +135,27 @@ const emailService = {
let { attDataList, html } = await attService.toImageUrlHtml(c, content, r2Domain);
if (attDataList.length > 0 && !r2Domain) {
throw new BizError('r2域名未配置不能发送正文图片');
throw new BizError(t('noOsDomainSendPic'));
}
if (attDataList.length > 0 && !c.env.r2) {
throw new BizError('r2对象存储未配置不能发送正文图片');
throw new BizError(t('noOsSendPic'));
}
if (attachments.length > 0 && !r2Domain) {
throw new BizError('r2域名未配置不能发送附件');
throw new BizError(t('noOsDomainSendAtt'));
}
if (attachments.length > 0 && !c.env.r2) {
throw new BizError('r2对象存储未配置不能发送附件');
throw new BizError(t('noOsSendAtt'));
}
if (send === settingConst.send.CLOSE) {
throw new BizError('邮件发送功能已停用', 403);
throw new BizError(t('disabledSend'), 403);
}
if (attachments.length > 0 && manyType === 'divide') {
throw new BizError('分别发送暂时不支持附件');
throw new BizError(t('noSeparateSend'));
}
@@ -164,17 +165,17 @@ const emailService = {
if (c.env.admin !== userRow.email && roleRow.sendCount) {
if (roleRow.sendCount < 0) {
throw new BizError('用户无发送次数', 403);
throw new BizError(t('userNoSendTotal'), 403);
}
if (userRow.sendCount >= roleRow.sendCount) {
if (roleRow.sendType === 'day') throw new BizError('发送次数已到达每日限制', 403);
if (roleRow.sendType === 'count') throw new BizError('发送次数已到达限制', 403);
if (roleRow.sendType === 'day') throw new BizError(t('daySendLimit'), 403);
if (roleRow.sendType === 'count') throw new BizError(t('totalSendLimit'), 403);
}
if (userRow.sendCount + receiveEmail.length > roleRow.sendCount) {
if (roleRow.sendType === 'day') throw new BizError('当日剩余发送次数不足', 403);
if (roleRow.sendType === 'count') throw new BizError('剩余发送次数不足', 403);
if (roleRow.sendType === 'day') throw new BizError(t('daySendLack'), 403);
if (roleRow.sendType === 'count') throw new BizError(t('totalSendLack'), 403);
}
}
@@ -183,19 +184,19 @@ const emailService = {
const accountRow = await accountService.selectById(c, accountId);
if (!accountRow) {
throw new BizError('发件人邮箱不存在');
throw new BizError(t('senderAccountNotExist'));
}
const domain = emailUtils.getDomain(accountRow.email);
const resendToken = resendTokens[domain];
if (!resendToken) {
throw new BizError('resend密钥未配置');
throw new BizError(t('noResendToken'));
}
if (accountRow.userId !== userId) {
throw new BizError('发件人邮箱非当前用户所有');
throw new BizError(t('sendEmailNotCurUser'));
}
if (!name) {
@@ -211,7 +212,7 @@ const emailService = {
emailRow = await this.selectById(c, emailId);
if (!emailRow) {
throw new BizError('邮件不存在无法回复');
throw new BizError(t('notExistEmailReply'));
}
}

View File

@@ -16,7 +16,8 @@ import turnstileService from './turnstile-service';
import roleService from './role-service';
import regKeyService from './reg-key-service';
import dayjs from 'dayjs';
import { formatDetailDate, toUtc } from '../utils/date-uitil';
import { toUtc } from '../utils/date-uitil';
import { t } from '../i18n/i18n.js';
const loginService = {
@@ -27,27 +28,27 @@ const loginService = {
const {regKey, register} = await settingService.query(c)
if (register === settingConst.register.CLOSE) {
throw new BizError('注册功能已关闭');
throw new BizError(t('regDisabled'));
}
if (!verifyUtils.isEmail(email)) {
throw new BizError('非法邮箱');
throw new BizError(t('notEmail'));
}
if (password.length > 30) {
throw new BizError('密码长度超出限制');
throw new BizError(t('pwdLengthLimit'));
}
if (emailUtils.getName(email).length > 30) {
throw new BizError('邮箱长度超出限制');
throw new BizError(t('emailLengthLimit'));
}
if (password.length < 6) {
throw new BizError('密码至少6位');
throw new BizError(t('pwdMinLengthLimit'));
}
if (!c.env.domain.includes(emailUtils.getDomain(email))) {
throw new BizError('非法邮箱域名');
throw new BizError(t('notEmailDomain'));
}
let type = null;
@@ -68,11 +69,11 @@ const loginService = {
const accountRow = await accountService.selectByEmailIncludeDelNoCase(c, email);
if (accountRow && accountRow.isDel === isDel.DELETE) {
throw new BizError('该邮箱已被注销');
throw new BizError(t('isDelUser'));
}
if (accountRow) {
throw new BizError('该邮箱已被注册');
throw new BizError(t('isRegAccount'));
}
if (await settingService.isRegisterVerify(c)) {
@@ -103,24 +104,24 @@ const loginService = {
async handleOpenRegKey(c, regKey, code) {
if (!code) {
throw new BizError('注册码不能为空');
throw new BizError(t('emptyRegKey'));
}
const regKeyRow = await regKeyService.selectByCode(c, code);
if (!regKeyRow) {
throw new BizError('注册码不存在');
throw new BizError(t('notExistRegKey'));
}
if (regKeyRow.count <= 0) {
throw new BizError('注册码使用次数已耗尽');
throw new BizError(t('noRegKeyCount'));
}
const today = toUtc().tz('Asia/Shanghai').startOf('day')
const expireTime = toUtc(regKeyRow.expireTime).tz('Asia/Shanghai').startOf('day');
if (expireTime.isBefore(today)) {
throw new BizError('注册码已过期');
throw new BizError(t('regKeyExpire'));
}
return { type: regKeyRow.roleId, regKeyId: regKeyRow.regKeyId };
@@ -153,25 +154,25 @@ const loginService = {
const { email, password } = params;
if (!email || !password) {
throw new BizError('邮箱和密码不能为空');
throw new BizError(t('emailAndPwdEmpty'));
}
const userRow = await userService.selectByEmailIncludeDel(c, email);
if (!userRow) {
throw new BizError('该用户不存在');
throw new BizError(t('notExistUser'));
}
if(userRow.isDel === isDel.DELETE) {
throw new BizError('该用户已被注销');
throw new BizError(t('isDelUser'));
}
if(userRow.status === userConst.status.BAN) {
throw new BizError('该用户已被禁用');
throw new BizError(t('isBanUser'));
}
if (!await cryptoUtils.verifyPassword(password, userRow.salt, userRow.password)) {
throw new BizError('密码输入错误');
throw new BizError(t('IncorrectPwd'));
}
const uuid = uuidv4();

View File

@@ -5,13 +5,19 @@ import rolePerm from '../entity/role-perm';
import user from '../entity/user';
import role from '../entity/role';
import { permConst } from '../const/entity-const';
import { t } from '../i18n/i18n'
const permService = {
async tree(c) {
const pList = await orm(c).select().from(perm).where(eq(perm.pid, 0)).orderBy(asc(perm.sort)).all();
const cList = await orm(c).select().from(perm).where(ne(perm.pid, 0)).orderBy(asc(perm.sort)).all();
cList.forEach(cItem => {
cItem.name = t('perms.' + cItem.name)
})
pList.forEach(pItem => {
pItem.name = t('perms.' + pItem.name)
pItem.children = cList.filter(cItem => cItem.pid === pItem.permId)
})
return pList;

View File

@@ -5,6 +5,8 @@ import roleService from './role-service';
import BizError from '../error/biz-error';
import { formatDetailDate, toUtc } from '../utils/date-uitil';
import userService from './user-service';
import { t } from '../i18n/i18n.js';
const regKeyService = {
async add(c, params, userId) {
@@ -12,26 +14,26 @@ const regKeyService = {
let {code,roleId,count,expireTime} = params;
if (!code) {
throw new BizError('注册码不能为空');
throw new BizError(t('emptyRegKey'));
}
if (!count) {
throw new BizError('使用次数不能为空');
throw new BizError(t('emptyRegKey'));
}
if (!expireTime) {
throw new BizError('有效时间不能为空');
throw new BizError(t('emptyRegKeyExpire'));
}
const regKeyRow = await orm(c).select().from(regKey).where(eq(regKey.code, code)).get();
if (regKeyRow) {
throw new BizError('注册码已存在');
throw new BizError(t('isExistRegKye'));
}
const roleRow = roleService.selectById(c, roleId);
if (!roleRow) {
throw new BizError('权限身份不存在');
throw new BizError(t('roleNotExist'));
}
expireTime = formatDetailDate(expireTime)

View File

@@ -7,8 +7,8 @@ import perm from '../entity/perm';
import { permConst, roleConst } from '../const/entity-const';
import userService from './user-service';
import user from '../entity/user';
import emailUtils from '../utils/email-utils';
import verifyUtils from '../utils/verify-utils';
import { t } from '../i18n/i18n.js';
const roleService = {
@@ -17,19 +17,19 @@ const roleService = {
let { name, permIds, banEmail } = params;
if (!name) {
throw new BizError('身份名不能为空');
throw new BizError(t('emptyRoleName'));
}
let roleRow = await orm(c).select().from(role).where(eq(role.name, name)).get();
if (roleRow) {
throw new BizError('身份名已存在');
throw new BizError(t('roleNameExist'));
}
const notEmailIndex = banEmail.findIndex(item => !verifyUtils.isEmail(item))
if (notEmailIndex > -1) {
throw new BizError('非法邮箱');
throw new BizError(t('notEmail'));
}
banEmail = banEmail.join(',')
@@ -67,7 +67,7 @@ const roleService = {
let { name, permIds, roleId, banEmail } = params;
if (!name) {
throw new BizError('名字不能为空');
throw new BizError(t('emptyRoleName'));
}
delete params.isDefault
@@ -75,7 +75,7 @@ const roleService = {
const notEmailIndex = banEmail.findIndex(item => !verifyUtils.isEmail(item))
if (notEmailIndex > -1) {
throw new BizError('非法邮箱');
throw new BizError(t('notEmail'));
}
banEmail = banEmail.join(',')
@@ -97,11 +97,11 @@ const roleService = {
const roleRow = await orm(c).select().from(role).where(eq(role.roleId, roleId)).get();
if (!roleRow) {
throw new BizError('身份不存在');
throw new BizError(t('notExist'));
}
if (roleRow.isDefault) {
throw new BizError('默认身份不能删除');
throw new BizError(t('delDefRole'));
}
const defRoleRow = await orm(c).select().from(role).where(eq(role.isDefault, roleConst.isDefault.OPEN)).get();
@@ -124,7 +124,7 @@ const roleService = {
async setDefault(c, params) {
const roleRow = await orm(c).select().from(role).where(eq(role.roleId, params.roleId)).get();
if (!roleRow) {
throw new BizError('身份不存在');
throw new BizError(t('roleNotExist'));
}
await orm(c).update(role).set({ isDefault: 0 }).run();
await orm(c).update(role).set({ isDefault: 1 }).where(eq(role.roleId, params.roleId)).run();

View File

@@ -9,6 +9,7 @@ import accountService from './account-service';
import userService from './user-service';
import constant from '../const/constant';
import BizError from '../error/biz-error';
import { t } from '../i18n/i18n'
const settingService = {
@@ -22,7 +23,7 @@ const settingService = {
const setting = await c.env.kv.get(KvConst.SETTING, { type: 'json' });
let domainList = c.env.domain;
if (typeof domainList === 'string') {
throw new BizError('环境变量domain必须是JSON类型');
throw new BizError(t('notJsonDomain'));
}
domainList = domainList.map(item => '@' + item);
setting.domainList = domainList;
@@ -70,11 +71,11 @@ const settingService = {
if (!c.env.r2) {
throw new BizError('r2对象存储未配置不能上传背景');
throw new BizError(t('noOsUpBack'));
}
if (!settingRow.r2Domain) {
throw new BizError('r2域名未配置不上传背景');
throw new BizError(t('noOsDomainUpBack'));
}
const { background } = params;

View File

@@ -6,17 +6,17 @@ import { and, desc, eq, lt, sql, inArray } from 'drizzle-orm';
import email from '../entity/email';
import { isDel } from '../const/entity-const';
import attService from "./att-service";
import { t } from '../i18n/i18n'
const starService = {
async add(c, params, userId) {
const { emailId } = params;
const email = await emailService.selectById(c, emailId);
if (!email) {
throw new BizError('星标的邮件不存在');
throw new BizError(t('starNotExistEmail'));
}
if (!email.userId === userId) {
throw new BizError('星标的邮件非当前用户所有');
throw new BizError(t('starNotExistEmail'));
}
const exist = await orm(c).select().from(star).where(
and(

View File

@@ -1,12 +1,13 @@
import BizError from '../error/biz-error';
import settingService from './setting-service';
import { t } from '../i18n/i18n'
const turnstileService = {
async verify(c, token) {
if (!token) {
throw new BizError('验证token不能为空');
throw new BizError(t('emptyBotToken'));
}
const settingRow = await settingService.query(c)
@@ -26,7 +27,7 @@ const turnstileService = {
const result = await res.json();
if (!result.success) {
throw new BizError('人机验证失败,请重试',400)
throw new BizError(t('botVerifyFail'),400)
}
}
};

View File

@@ -2,7 +2,7 @@ import BizError from '../error/biz-error';
import accountService from './account-service';
import orm from '../entity/orm';
import user from '../entity/user';
import { and, asc, count, desc, eq, inArray, like, sql } from 'drizzle-orm';
import { and, asc, count, desc, eq, inArray, sql } from 'drizzle-orm';
import { emailConst, isDel, roleConst, userConst } from '../const/entity-const';
import kvConst from '../const/kv-const';
import KvConst from '../const/kv-const';
@@ -15,6 +15,7 @@ import roleService from './role-service';
import emailUtils from '../utils/email-utils';
import saltHashUtils from '../utils/crypto-utils';
import constant from '../const/constant';
import { t } from '../i18n/i18n'
const userService = {
@@ -50,7 +51,7 @@ const userService = {
const { password } = params;
if (password < 6) {
throw new BizError('密码不能小于6位');
throw new BizError(t('pwdMinLengthLimit'));
}
const { salt, hash } = await cryptoUtils.hashPassword(password);
await orm(c).update(user).set({ password: hash, salt: salt }).where(eq(user.userId, userId)).run();
@@ -295,7 +296,7 @@ const userService = {
const roleRow = await roleService.selectById(c, type);
if (!roleRow) {
throw new BizError('身份不存在');
throw new BizError(t('roleNotExist'));
}
await orm(c)
@@ -327,27 +328,27 @@ const userService = {
const { email, type, password } = params;
if (!c.env.domain.includes(emailUtils.getDomain(email))) {
throw new BizError('非法邮箱域名');
throw new BizError(t('notEmailDomain'));
}
if (password.length < 6) {
throw new BizError('密码必须大于6位');
throw new BizError(t('pwdMinLengthLimit'));
}
const accountRow = await accountService.selectByEmailIncludeDel(c, email);
if (accountRow && accountRow.isDel === isDel.DELETE) {
throw new BizError('该邮箱已被注销');
throw new BizError(t('isDelUser'));
}
if (accountRow) {
throw new BizError('该邮箱已被注册');
throw new BizError(t('isRegAccount'));
}
const role = roleService.selectById(c, type);
if (!role) {
throw new BizError('权限身份不存在');
throw new BizError(t('roleNotExist'));
}
const { salt, hash } = await saltHashUtils.hashPassword(password);