新增支持s3协议对象存储
This commit is contained in:
@@ -21,8 +21,3 @@ app.put('/setting/setBackground', async (c) => {
|
||||
const key = await settingService.setBackground(c, await c.req.json());
|
||||
return c.json(result.ok(key));
|
||||
});
|
||||
|
||||
app.delete('/setting/physicsDeleteAll', async (c) => {
|
||||
await settingService.physicsDeleteAll(c);
|
||||
return c.json(result.ok());
|
||||
});
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
import app from '../hono/hono';
|
||||
import result from '../model/result';
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ export const setting = sqliteTable('setting', {
|
||||
noticeWidth: integer('notice_width').default(400).notNull(),
|
||||
notice: integer('notice').default(0).notNull(),
|
||||
noRecipient: integer('no_recipient').default(1).notNull(),
|
||||
loginDomain: integer('login_domain').default(0).notNull()
|
||||
loginDomain: integer('login_domain').default(0).notNull(),
|
||||
bucket: text('bucket').default('').notNull(),
|
||||
region: text('region').default('').notNull(),
|
||||
endpoint: text('endpoint').default('').notNull(),
|
||||
s3AccessKey: text('s3_access_key').default('').notNull(),
|
||||
s3SecretKey: text('s3_secret_key').default('').notNull()
|
||||
});
|
||||
export default setting
|
||||
|
||||
@@ -11,10 +11,10 @@ const en = {
|
||||
delMyAccount: 'Cannot delete your own 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',
|
||||
noOsDomainSendPic: 'Cannot send body images: object storage domain not configured',
|
||||
noOsSendPic: 'Cannot send body images: object storage not configured',
|
||||
noOsDomainSendAtt: 'Cannot send attachments: object storage domain not configured',
|
||||
noOsSendAtt: 'Cannot send attachments: object storage not configured',
|
||||
disabledSend: 'Email sending feature is disabled',
|
||||
noSeparateSend: 'Attachments are not supported in separate sending',
|
||||
daySendLimit: 'Daily send limit reached',
|
||||
@@ -46,8 +46,8 @@ const en = {
|
||||
delDefRole: 'Default role cannot be deleted',
|
||||
notJsonDomain: 'Environment variable "domain" must be in JSON format',
|
||||
noDomainVariable: 'Environment variable domain cannot be empty',
|
||||
noOsUpBack: 'Cannot upload background: R2 object storage not configured',
|
||||
noOsDomainUpBack: 'Cannot upload background: R2 domain not configured',
|
||||
noOsUpBack: 'Cannot upload background: object storage not configured',
|
||||
noOsDomainUpBack: 'Cannot upload background: object storage domain not configured',
|
||||
starNotExistEmail: 'Starred email does not exist',
|
||||
emptyBotToken: 'Please verify that you are human',
|
||||
botVerifyFail: 'Bot verification failed, please try again',
|
||||
|
||||
@@ -11,10 +11,10 @@ const zh = {
|
||||
delMyAccount: '不可以删除自己的邮箱',
|
||||
noUserAccount: '该邮箱不属于当前用户',
|
||||
usernameLengthLimit: '用户名长度超出限制',
|
||||
noOsDomainSendPic: 'r2域名未配置不能发送正文图片',
|
||||
noOsSendPic: 'r2对象存储未配置不能发送正文图片',
|
||||
noOsDomainSendAtt: 'r2域名未配置不能发送附件',
|
||||
noOsSendAtt: 'r2对象存储未配置不能发送附件',
|
||||
noOsDomainSendPic: '对象存储域名未配置不能发送正文图片',
|
||||
noOsSendPic: '对象存储未配置不能发送正文图片',
|
||||
noOsDomainSendAtt: '域名未配置不能发送附件',
|
||||
noOsSendAtt: '对象存储未配置不能发送附件',
|
||||
disabledSend: '邮件发送功能已停用',
|
||||
noSeparateSend: '分别发送暂时不支持附件',
|
||||
daySendLimit: '发送次数已到达每日限制',
|
||||
@@ -46,8 +46,8 @@ const zh = {
|
||||
delDefRole: '默认身份不能删除',
|
||||
notJsonDomain: '环境变量domain必须是JSON类型',
|
||||
noDomainVariable: '环境变量domain不能为空',
|
||||
noOsUpBack: 'r2对象存储未配置不能上传背景',
|
||||
noOsDomainUpBack: 'r2域名未配置不能上传背景',
|
||||
noOsUpBack: '对象存储未配置不能上传背景',
|
||||
noOsDomainUpBack: '对象存储域名未配置不能上传背景',
|
||||
starNotExistEmail: '星标的邮件不存在',
|
||||
emptyBotToken: '需要进行人机验证',
|
||||
botVerifyFail: '人机验证失败,请重试',
|
||||
|
||||
@@ -21,10 +21,26 @@ const init = {
|
||||
await this.v1_5DB(c);
|
||||
await this.v1_6DB(c);
|
||||
await this.v1_7DB(c);
|
||||
await this.v1_7DB(c);
|
||||
await this.v2DB(c);
|
||||
await settingService.refresh(c);
|
||||
return c.text(t('initSuccess'));
|
||||
},
|
||||
|
||||
async v2DB(c) {
|
||||
try {
|
||||
await c.env.db.batch([
|
||||
c.env.db.prepare(`ALTER TABLE setting ADD COLUMN bucket TEXT NOT NULL DEFAULT '';`),
|
||||
c.env.db.prepare(`ALTER TABLE setting ADD COLUMN region TEXT NOT NULL DEFAULT '';`),
|
||||
c.env.db.prepare(`ALTER TABLE setting ADD COLUMN endpoint TEXT NOT NULL DEFAULT '';`),
|
||||
c.env.db.prepare(`ALTER TABLE setting ADD COLUMN s3_access_key TEXT NOT NULL DEFAULT '';`),
|
||||
c.env.db.prepare(`ALTER TABLE setting ADD COLUMN s3_secret_key TEXT NOT NULL DEFAULT '';`)
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error(e.message)
|
||||
}
|
||||
},
|
||||
|
||||
async v1_7DB(c) {
|
||||
try {
|
||||
await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN login_domain INTEGER NOT NULL DEFAULT 0;`).run();
|
||||
@@ -308,7 +324,6 @@ const init = {
|
||||
(17, '系统设置', '', 0, 1, 6),
|
||||
(18, '设置查看', 'setting:query', 17, 2, 0),
|
||||
(19, '设置修改', 'setting:set', 17, 2, 1),
|
||||
(20, '物理清空', 'setting:clean', 17, 2, 2),
|
||||
(21, '邮箱侧栏', '', 0, 0, 1),
|
||||
(22, '邮箱查看', 'account:query', 21, 2, 0),
|
||||
(23, '邮箱添加', 'account:add', 21, 2, 1),
|
||||
|
||||
@@ -33,7 +33,6 @@ const requirePerms = [
|
||||
'/role/setDefault',
|
||||
'/allEmail/list',
|
||||
'/allEmail/delete',
|
||||
'/setting/physicsDeleteAll',
|
||||
'/setting/setBackground',
|
||||
'/setting/set',
|
||||
'/setting/query',
|
||||
@@ -73,7 +72,6 @@ const premKey = {
|
||||
'all-email:delete': ['/allEmail/delete','/allEmail/batchDelete'],
|
||||
'setting:query': ['/setting/query'],
|
||||
'setting:set': ['/setting/set', '/setting/setBackground'],
|
||||
'setting:clean': ['/setting/physicsDeleteAll'],
|
||||
'analysis:query': ['/analysis/echarts'],
|
||||
'reg-key:add': ['/regKey/add'],
|
||||
'reg-key:query': ['/regKey/list','/regKey/history'],
|
||||
|
||||
@@ -156,19 +156,6 @@ const accountService = {
|
||||
await orm(c).insert(account).values(list).run();
|
||||
},
|
||||
|
||||
async physicsDeleteAll(c) {
|
||||
const accountIdsRow = await orm(c).select({accountId: account.accountId}).from(account).where(eq(account.isDel,isDel.DELETE)).limit(99);
|
||||
if (accountIdsRow.length === 0) {
|
||||
return;
|
||||
}
|
||||
const accountIds = accountIdsRow.map(item => item.accountId)
|
||||
await emailService.physicsDeleteAccountIds(c, accountIds);
|
||||
await orm(c).delete(account).where(inArray(account.accountId,accountIds)).run();
|
||||
if (accountIdsRow.length === 99) {
|
||||
await this.physicsDeleteAll(c)
|
||||
}
|
||||
},
|
||||
|
||||
async physicsDeleteByUserIds(c, userIds) {
|
||||
await emailService.physicsDeleteUserIds(c, userIds);
|
||||
await orm(c).delete(account).where(inArray(account.userId,userIds)).run();
|
||||
|
||||
@@ -117,63 +117,45 @@ const attService = {
|
||||
},
|
||||
|
||||
async removeByUserIds(c, userIds) {
|
||||
await this.removeAttByField(c, att.userId, userIds);
|
||||
await this.removeAttByField(c, 'user_id', userIds);
|
||||
},
|
||||
|
||||
async removeByEmailIds(c, emailIds) {
|
||||
await this.removeAttByField(c, att.emailId, emailIds);
|
||||
},
|
||||
|
||||
async removeByAccountIds(c, accountIds) {
|
||||
await this.removeAttByField(c, att.accountId, accountIds);
|
||||
},
|
||||
|
||||
async removeAttByField(c, fieldName, fieldValues) {
|
||||
|
||||
const condition = inArray(fieldName, fieldValues);
|
||||
const attList = await orm(c).select().from(att).where(condition).limit(99);
|
||||
|
||||
if (attList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attIds = attList.map(attRow => attRow.attId);
|
||||
const keys = attList.map(attRow => attRow.key);
|
||||
await orm(c).delete(att).where(inArray(att.attId, attIds)).run();
|
||||
|
||||
const existAttRows = await orm(c).select().from(att).where(inArray(att.key, keys)).all();
|
||||
const existKeys = existAttRows.map(attRow => attRow.key);
|
||||
const delKeyList = keys.filter(key => !existKeys.includes(key));
|
||||
if (delKeyList.length > 0) {
|
||||
await c.env.r2.delete(delKeyList);
|
||||
}
|
||||
|
||||
if (attList.length >= 99) {
|
||||
await this.removeAttByField(c, fieldName, fieldValues);
|
||||
}
|
||||
await this.removeAttByField(c, 'email_id', emailIds);
|
||||
},
|
||||
|
||||
selectByEmailIds(c, emailIds) {
|
||||
return orm(c).select().from(att).where(
|
||||
and(
|
||||
inArray(att.emailId,emailIds),
|
||||
inArray(att.emailId, emailIds),
|
||||
eq(att.type, attConst.type.ATT)
|
||||
))
|
||||
.all();
|
||||
},
|
||||
|
||||
async deleteByEmailIds(c, emailIds) {
|
||||
async removeAttByField(c, fieldName, fieldValues) {
|
||||
|
||||
const queryAttSql = fieldValues.map(value =>
|
||||
c.env.db.prepare(`SELECT a.key, a.att_id
|
||||
FROM attachments a
|
||||
JOIN (SELECT key
|
||||
FROM attachments
|
||||
GROUP BY key
|
||||
HAVING COUNT (*) = 1) t
|
||||
ON a.key = t.key
|
||||
WHERE a.${fieldName} = ?;`).bind(value));
|
||||
|
||||
const queryAttSql = emailIds.map(emailId => c.env.db.prepare(`SELECT key,att_id FROM attachments WHERE email_id = ${emailId} GROUP BY key HAVING COUNT(*) = 1;`))
|
||||
const attListResult = await c.env.db.batch(queryAttSql);
|
||||
|
||||
const delKeyList = attListResult.flatMap(r => r.results.map(row => row.key));
|
||||
|
||||
if (delKeyList.length > 0) {
|
||||
await this.batchDelete(c, delKeyList)
|
||||
await this.batchDelete(c, delKeyList);
|
||||
}
|
||||
|
||||
const delAttSql = emailIds.map(emailId => c.env.db.prepare(`DELETE FROM attachments WHERE email_id = ${emailId}`))
|
||||
const delAttSql = fieldValues.map(value => c.env.db.prepare(`DELETE
|
||||
FROM attachments
|
||||
WHERE ${fieldName} = ?`).bind(value));
|
||||
await c.env.db.batch(delAttSql);
|
||||
},
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import starService from './star-service';
|
||||
import dayjs from 'dayjs';
|
||||
import kvConst from '../const/kv-const';
|
||||
import { t } from '../i18n/i18n'
|
||||
import r2Service from './r2-service';
|
||||
|
||||
const emailService = {
|
||||
|
||||
@@ -128,7 +129,18 @@ const emailService = {
|
||||
|
||||
async send(c, params, userId) {
|
||||
|
||||
let { accountId, name, sendType, emailId, receiveEmail, manyType, text, content, subject, attachments } = params;
|
||||
let {
|
||||
accountId,
|
||||
name,
|
||||
sendType,
|
||||
emailId,
|
||||
receiveEmail,
|
||||
manyType,
|
||||
text,
|
||||
content,
|
||||
subject,
|
||||
attachments
|
||||
} = params;
|
||||
|
||||
const { resendTokens, r2Domain, send } = await settingService.query(c);
|
||||
|
||||
@@ -164,7 +176,7 @@ const emailService = {
|
||||
throw new BizError(t('noOsDomainSendPic'));
|
||||
}
|
||||
|
||||
if (attDataList.length > 0 && !c.env.r2) {
|
||||
if (attDataList.length > 0 && !await r2Service.hasOSS(c)) {
|
||||
throw new BizError(t('noOsSendPic'));
|
||||
}
|
||||
|
||||
@@ -172,7 +184,7 @@ const emailService = {
|
||||
throw new BizError(t('noOsDomainSendAtt'));
|
||||
}
|
||||
|
||||
if (attachments.length > 0 && !c.env.r2) {
|
||||
if (attachments.length > 0 && !await r2Service.hasOSS(c)) {
|
||||
throw new BizError(t('noOsSendAtt'));
|
||||
}
|
||||
|
||||
@@ -342,7 +354,7 @@ const emailService = {
|
||||
await attService.saveArticleAtt(c, attDataList, userId, accountId, emailRow.emailId);
|
||||
}
|
||||
|
||||
if (attachments?.length > 0 && c.env.r2) {
|
||||
if (attachments?.length > 0 && await r2Service.hasOSS(c)) {
|
||||
await attService.saveSendAtt(c, attachments, userId, accountId, emailRow.emailId);
|
||||
}
|
||||
|
||||
@@ -443,21 +455,6 @@ const emailService = {
|
||||
return list;
|
||||
},
|
||||
|
||||
|
||||
async physicsDeleteAll(c) {
|
||||
const emailIdsRow = await orm(c).select({ emailId: email.emailId }).from(email).where(eq(email.isDel, isDel.DELETE)).limit(99);
|
||||
if (emailIdsRow.length === 0) {
|
||||
return;
|
||||
}
|
||||
const emailIds = emailIdsRow.map(row => row.emailId);
|
||||
await attService.removeByEmailIds(c, emailIds);
|
||||
await starService.removeByEmailIds(c, emailIds);
|
||||
await orm(c).delete(email).where(inArray(email.emailId, emailIds)).run();
|
||||
if (emailIdsRow.length === 99) {
|
||||
await this.physicsDeleteAll(c);
|
||||
}
|
||||
},
|
||||
|
||||
async physicsDelete(c, params) {
|
||||
let { emailIds } = params;
|
||||
emailIds = emailIds.split(',').map(Number);
|
||||
@@ -466,11 +463,6 @@ const emailService = {
|
||||
await orm(c).delete(email).where(inArray(email.emailId, emailIds)).run();
|
||||
},
|
||||
|
||||
async physicsDeleteAccountIds(c, accountIds) {
|
||||
await attService.removeByAccountIds(c, accountIds);
|
||||
await orm(c).delete(email).where(inArray(email.accountId, accountIds)).run();
|
||||
},
|
||||
|
||||
async physicsDeleteUserIds(c, userIds) {
|
||||
await attService.removeByUserIds(c, userIds);
|
||||
await orm(c).delete(email).where(inArray(email.userId, userIds)).run();
|
||||
@@ -657,7 +649,7 @@ const emailService = {
|
||||
return;
|
||||
}
|
||||
|
||||
await attService.deleteByEmailIds(c, emailIds);
|
||||
await attService.removeByEmailIds(c, emailIds);
|
||||
|
||||
await orm(c).delete(email).where(conditions.length > 1 ? and(...conditions) : conditions[0]).run();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,34 @@
|
||||
import s3Service from './s3-service';
|
||||
import settingService from './setting-service';
|
||||
|
||||
const r2Service = {
|
||||
|
||||
async hasOSS(c) {
|
||||
|
||||
if (c.env.r2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const setting = await settingService.query(c);
|
||||
const { bucket, region, endpoint, s3AccessKey, s3SecretKey } = setting;
|
||||
|
||||
return !!(bucket && region && endpoint && s3AccessKey && s3SecretKey);
|
||||
},
|
||||
|
||||
async putObj(c, key, content, metadata) {
|
||||
await c.env.r2.put(key, content, {
|
||||
httpMetadata: {...metadata}
|
||||
});
|
||||
|
||||
if (c.env.r2) {
|
||||
|
||||
await c.env.r2.put(key, content, {
|
||||
httpMetadata: { ...metadata }
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
await s3Service.putObj(c, key, content, metadata);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async getObj(c, key) {
|
||||
@@ -10,7 +36,17 @@ const r2Service = {
|
||||
},
|
||||
|
||||
async delete(c, key) {
|
||||
await c.env.r2.delete(key);
|
||||
|
||||
if (c.env.r2) {
|
||||
|
||||
await c.env.r2.delete(key);
|
||||
|
||||
} else {
|
||||
|
||||
await s3Service.deleteObj(c, key);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
54
mail-worker/src/service/s3-service.js
Normal file
54
mail-worker/src/service/s3-service.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { S3Client, PutObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
||||
import settingService from './setting-service';
|
||||
import domainUtils from '../utils/domain-uitls';
|
||||
const s3Service = {
|
||||
|
||||
async putObj(c, key, content, metadata) {
|
||||
|
||||
const client = await this.client(c);
|
||||
|
||||
const { bucket } = await settingService.query(c);
|
||||
|
||||
await client.send(
|
||||
new PutObjectCommand({ Bucket: bucket, Key: key, Body: content, ...metadata })
|
||||
)
|
||||
},
|
||||
|
||||
async deleteObj(c,keys) {
|
||||
|
||||
if (typeof keys === 'string') {
|
||||
keys = [keys]
|
||||
}
|
||||
|
||||
if (keys.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const client = await this.client(c)
|
||||
|
||||
const { bucket } = await settingService.query(c);
|
||||
|
||||
await client.send(
|
||||
new DeleteObjectsCommand({
|
||||
Bucket: bucket,
|
||||
Delete: {
|
||||
Objects: keys.map(key => ({Key: key}))
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
async client(c) {
|
||||
const { region, endpoint, s3AccessKey, s3SecretKey } = await settingService.query(c);
|
||||
return new S3Client({
|
||||
region: region,
|
||||
endpoint: domainUtils.toOssDomain(endpoint),
|
||||
credentials: {
|
||||
accessKeyId: s3AccessKey,
|
||||
secretAccessKey: s3SecretKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default s3Service
|
||||
@@ -17,12 +17,18 @@ const settingService = {
|
||||
async refresh(c) {
|
||||
const settingRow = await orm(c).select().from(setting).get();
|
||||
settingRow.resendTokens = JSON.parse(settingRow.resendTokens);
|
||||
c.set('setting', settingRow);
|
||||
await c.env.kv.put(KvConst.SETTING, JSON.stringify(settingRow));
|
||||
},
|
||||
|
||||
async query(c) {
|
||||
|
||||
if (c.get('setting')) {
|
||||
return c.get('setting')
|
||||
}
|
||||
|
||||
const setting = await c.env.kv.get(KvConst.SETTING, { type: 'json' });
|
||||
|
||||
let domainList = c.env.domain;
|
||||
|
||||
if (typeof domainList === 'string') {
|
||||
@@ -39,6 +45,7 @@ const settingService = {
|
||||
|
||||
domainList = domainList.map(item => '@' + item);
|
||||
setting.domainList = domainList;
|
||||
c.set('setting', setting);
|
||||
return setting;
|
||||
},
|
||||
|
||||
@@ -49,11 +56,17 @@ const settingService = {
|
||||
verifyRecordService.selectListByIP(c)
|
||||
]);
|
||||
|
||||
settingRow.siteKey = settingRow.siteKey ? `${settingRow.siteKey.slice(0, 12)}******` : null;
|
||||
settingRow.secretKey = settingRow.secretKey ? `${settingRow.secretKey.slice(0, 12)}******` : null;
|
||||
|
||||
Object.keys(settingRow.resendTokens).forEach(key => {
|
||||
settingRow.resendTokens[key] = `${settingRow.resendTokens[key].slice(0, 12)}******`;
|
||||
});
|
||||
|
||||
settingRow.s3AccessKey = settingRow.s3AccessKey ? `${settingRow.s3AccessKey.slice(0, 12)}******` : null;
|
||||
settingRow.s3SecretKey = settingRow.s3SecretKey ? `${settingRow.s3SecretKey.slice(0, 12)}******` : null;
|
||||
settingRow.hasR2 = !!c.env.r2
|
||||
|
||||
let regVerifyOpen = false
|
||||
let addVerifyOpen = false
|
||||
|
||||
@@ -91,7 +104,7 @@ const settingService = {
|
||||
|
||||
if (background && !background.startsWith('http')) {
|
||||
|
||||
if (!c.env.r2) {
|
||||
if (!await r2Service.hasOSS(c)) {
|
||||
throw new BizError(t('noOsUpBack'));
|
||||
}
|
||||
|
||||
@@ -124,12 +137,6 @@ const settingService = {
|
||||
return background;
|
||||
},
|
||||
|
||||
async physicsDeleteAll(c) {
|
||||
await emailService.physicsDeleteAll(c);
|
||||
await accountService.physicsDeleteAll(c);
|
||||
await userService.physicsDeleteAll(c);
|
||||
},
|
||||
|
||||
async websiteConfig(c) {
|
||||
|
||||
const settingRow = await this.get(c)
|
||||
|
||||
@@ -87,20 +87,6 @@ const userService = {
|
||||
await c.env.kv.delete(kvConst.AUTH_INFO + userId)
|
||||
},
|
||||
|
||||
|
||||
async physicsDeleteAll(c) {
|
||||
const userIdsRow = await orm(c).select().from(user).where(eq(user.isDel, isDel.DELETE)).limit(99);
|
||||
if (userIdsRow.length === 0) {
|
||||
return;
|
||||
}
|
||||
const userIds = userIdsRow.map(item => item.userId);
|
||||
await accountService.physicsDeleteByUserIds(c, userIds);
|
||||
await orm(c).delete(user).where(inArray(user.userId, userIds)).run();
|
||||
if (userIdsRow.length === 99) {
|
||||
await this.physicsDeleteAll(c);
|
||||
}
|
||||
},
|
||||
|
||||
async physicsDelete(c, params) {
|
||||
const { userId } = params
|
||||
await accountService.physicsDeleteByUserIds(c, [userId])
|
||||
|
||||
20
mail-worker/src/utils/domain-uitls.js
Normal file
20
mail-worker/src/utils/domain-uitls.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const domainUtils = {
|
||||
toOssDomain(domain) {
|
||||
|
||||
if (!domain) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!domain.startsWith('http')) {
|
||||
return 'https://' + domain
|
||||
}
|
||||
|
||||
if (domain.endsWith("/")) {
|
||||
domain = domain.slice(0, -1);
|
||||
}
|
||||
|
||||
return domain
|
||||
}
|
||||
}
|
||||
|
||||
export default domainUtils
|
||||
Reference in New Issue
Block a user