新增中英文切换
This commit is contained in:
@@ -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));
|
||||
})
|
||||
@@ -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));
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ const constant = {
|
||||
ATTACHMENT_PREFIX: 'attachments/',
|
||||
BACKGROUND_PREFIX: 'static/background/',
|
||||
ADMIN_ROLE: {
|
||||
name: '超级管理员',
|
||||
name: 'admin',
|
||||
sendCount: 0,
|
||||
sendType: 'count',
|
||||
accountCount: 0
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
93
mail-worker/src/i18n/en.js
Normal file
93
mail-worker/src/i18n/en.js
Normal 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;
|
||||
30
mail-worker/src/i18n/i18n.js
Normal file
30
mail-worker/src/i18n/i18n.js
Normal 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;
|
||||
93
mail-worker/src/i18n/zh.js
Normal file
93
mail-worker/src/i18n/zh.js
Normal 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
|
||||
@@ -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 (
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user