保存
This commit is contained in:
127
mail-worker/src/service/account-service.js
Normal file
127
mail-worker/src/service/account-service.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import BizError from '../error/biz-error';
|
||||
import verifyUtils from '../utils/verify-utils';
|
||||
import emailUtils from '../utils/email-utils';
|
||||
import userService from './user-service';
|
||||
import emailService from './email-service';
|
||||
import orm from '../entity/orm';
|
||||
import account from '../entity/account';
|
||||
import { and, asc, eq, gt } from 'drizzle-orm';
|
||||
import { isDel } from '../const/entity-const';
|
||||
import settingService from './setting-service';
|
||||
import turnstileService from './turnstile-service';
|
||||
|
||||
const accountService = {
|
||||
|
||||
async add(c, params, userId) {
|
||||
|
||||
|
||||
if (!await settingService.isAddEmail(c)) {
|
||||
throw new BizError('添加邮箱功能已关闭');
|
||||
}
|
||||
|
||||
let { email, token } = params;
|
||||
|
||||
if (!email) {
|
||||
throw new BizError('邮箱不能为空');
|
||||
}
|
||||
|
||||
if (!verifyUtils.isEmail(email)) {
|
||||
throw new BizError('非法邮箱');
|
||||
}
|
||||
|
||||
if (!c.env.domain.includes(emailUtils.getDomain(email))) {
|
||||
throw new BizError('未配置改邮箱域名');
|
||||
}
|
||||
|
||||
const accountRow = await this.selectByEmailIncludeDel(c, email);
|
||||
|
||||
if (accountRow && accountRow.isDel === isDel.DELETE) {
|
||||
throw new BizError('该邮箱已被注销');
|
||||
}
|
||||
|
||||
if (accountRow) {
|
||||
throw new BizError('该邮箱已被注册');
|
||||
}
|
||||
|
||||
if (await settingService.isAddEmailVerify(c)) {
|
||||
await turnstileService.verify(c, token);
|
||||
}
|
||||
|
||||
return orm(c).insert(account).values({ email: email, userId: userId }).returning().get();
|
||||
},
|
||||
|
||||
selectByEmailIncludeDel(c, email) {
|
||||
return orm(c).select().from(account).where(eq(account.email, email)).get();
|
||||
},
|
||||
|
||||
selectByEmail(c, email) {
|
||||
return orm(c).select().from(account).where(
|
||||
and(
|
||||
eq(account.email, email),
|
||||
eq(account.isDel, isDel.NORMAL)))
|
||||
.get();
|
||||
},
|
||||
|
||||
list(c, params, userId) {
|
||||
|
||||
let { accountId, size } = params;
|
||||
|
||||
accountId = Number(accountId);
|
||||
size = Number(size);
|
||||
|
||||
if (size > 30) {
|
||||
size = 30;
|
||||
}
|
||||
|
||||
if (!accountId) {
|
||||
accountId = 0;
|
||||
}
|
||||
return orm(c).select().from(account).where(
|
||||
and(
|
||||
eq(account.userId, userId),
|
||||
eq(account.isDel, isDel.NORMAL),
|
||||
gt(account.accountId, accountId)))
|
||||
.orderBy(asc(account.accountId))
|
||||
.limit(size)
|
||||
.all();
|
||||
},
|
||||
|
||||
async delete(c, params, userId) {
|
||||
|
||||
let { accountId } = params;
|
||||
|
||||
const user = await userService.selectById(c, userId);
|
||||
const accountRow = await this.selectById(c, accountId);
|
||||
|
||||
if (accountRow.email === user.email) {
|
||||
throw new BizError('不可以删除自己的邮箱');
|
||||
}
|
||||
|
||||
if (accountRow.userId !== user.userId) {
|
||||
throw new BizError('该邮箱不属于当前用户');
|
||||
}
|
||||
|
||||
await orm(c).update(account).set({ isDel: isDel.DELETE }).where(
|
||||
and(eq(account.userId, userId),
|
||||
eq(account.accountId, accountId)))
|
||||
.run();
|
||||
await emailService.removeByAccountId(c, accountId);
|
||||
},
|
||||
|
||||
selectById(c, accountId) {
|
||||
return orm(c).select().from(account).where(
|
||||
and(eq(account.accountId, accountId),
|
||||
eq(account.isDel, isDel.NORMAL)))
|
||||
.get();
|
||||
},
|
||||
|
||||
async insert(c, params) {
|
||||
await orm(c).insert(account).values({ ...params }).returning();
|
||||
},
|
||||
|
||||
async removeByUserId(c, userId) {
|
||||
await orm(c).update(account).set({ isDel: isDel.DELETE }).where(eq(account.userId, userId)).run();
|
||||
}
|
||||
};
|
||||
|
||||
export default accountService;
|
||||
22
mail-worker/src/service/att-service.js
Normal file
22
mail-worker/src/service/att-service.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import orm from '../entity/orm';
|
||||
import { att } from '../entity/att';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
|
||||
const attService = {
|
||||
async addAtt(c, params) {
|
||||
await orm(c).insert(att).values(params).run();
|
||||
},
|
||||
async list(c, params, userId) {
|
||||
const { emailId } = params;
|
||||
|
||||
const list = await orm(c).select().from(att).where(
|
||||
and(
|
||||
eq(att.emailId, emailId),
|
||||
eq(att.userId, userId)))
|
||||
.all();
|
||||
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
export default attService;
|
||||
102
mail-worker/src/service/email-service.js
Normal file
102
mail-worker/src/service/email-service.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import orm from '../entity/orm';
|
||||
import email from '../entity/email';
|
||||
import { isDel } from '../const/entity-const';
|
||||
import { and, desc, eq, gt, inArray, lt, sql } from 'drizzle-orm';
|
||||
import { star } from '../entity/star';
|
||||
|
||||
const emailService = {
|
||||
|
||||
async list(c, params, userId) {
|
||||
|
||||
let { emailId, accountId, size } = params;
|
||||
size = Number(size);
|
||||
emailId = Number(emailId);
|
||||
|
||||
if (size > 30) {
|
||||
size = 30;
|
||||
}
|
||||
|
||||
if (!emailId) {
|
||||
emailId = 9999999999;
|
||||
}
|
||||
|
||||
const list = await orm(c)
|
||||
.select({
|
||||
...email,
|
||||
starId: star.starId
|
||||
})
|
||||
.from(email)
|
||||
.leftJoin(
|
||||
star,
|
||||
and(
|
||||
eq(star.emailId, email.emailId),
|
||||
eq(star.userId, userId)
|
||||
)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
lt(email.emailId, emailId),
|
||||
eq(email.accountId, accountId),
|
||||
eq(email.userId, userId),
|
||||
eq(email.isDel, isDel.NORMAL)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(email.emailId))
|
||||
.limit(size)
|
||||
.all();
|
||||
|
||||
const resultList = list.map(item => ({
|
||||
...item,
|
||||
isStar: item.starId != null ? 1 : 0
|
||||
}));
|
||||
|
||||
return { list: resultList };
|
||||
},
|
||||
|
||||
async delete(c, params, userId) {
|
||||
const { emailIds } = params;
|
||||
const emailIdList = emailIds.split(',').map(Number);
|
||||
await orm(c).update(email).set({ isDel: isDel.DELETE }).where(
|
||||
and(
|
||||
eq(email.userId, userId),
|
||||
inArray(email.emailId, emailIdList)))
|
||||
.run();
|
||||
},
|
||||
|
||||
receive(c, params) {
|
||||
return orm(c).insert(email).values({ ...params }).returning().get();
|
||||
},
|
||||
|
||||
async removeByAccountId(c, accountId) {
|
||||
await orm(c).update(email)
|
||||
.set({ isDel: isDel.DELETE })
|
||||
.where(eq(email.accountId, accountId))
|
||||
.run();
|
||||
},
|
||||
|
||||
async removeByUserId(c, userId) {
|
||||
await orm(c).update(email).set({ isDel: isDel.DELETE }).where(eq(email.userId, userId)).run();
|
||||
},
|
||||
selectById(c, emailId) {
|
||||
return orm(c).select().from(email).where(
|
||||
and(eq(email.emailId, emailId),
|
||||
eq(email.isDel, isDel.NORMAL)))
|
||||
.get();
|
||||
},
|
||||
|
||||
latest(c, params, userId) {
|
||||
let { emailId,accountId } = params;
|
||||
return orm(c).select().from(email).where(
|
||||
and(
|
||||
eq(email.userId, userId),
|
||||
eq(email.isDel, isDel.NORMAL),
|
||||
eq(email.accountId, accountId),
|
||||
gt(email.emailId, emailId)
|
||||
))
|
||||
.orderBy(desc(email.emailId))
|
||||
.limit(20);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default emailService;
|
||||
113
mail-worker/src/service/login-service.js
Normal file
113
mail-worker/src/service/login-service.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import BizError from '../error/biz-error';
|
||||
import userService from './user-service';
|
||||
import emailUtils from '../utils/email-utils';
|
||||
import { isDel, userConst } from '../const/entity-const';
|
||||
import JwtUtils from '../utils/jwt-utils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import KvConst from '../const/kv-const';
|
||||
import constant from '../const/constant';
|
||||
import userContext from '../security/user-context';
|
||||
import verifyUtils from '../utils/verify-utils';
|
||||
import accountService from './account-service';
|
||||
import settingService from './setting-service';
|
||||
import saltHashUtils from '../utils/crypto-utils';
|
||||
import cryptoUtils from '../utils/crypto-utils';
|
||||
import turnstileService from './turnstile-service';
|
||||
|
||||
|
||||
const loginService = {
|
||||
|
||||
async register(c, params) {
|
||||
|
||||
const { email, password, token } = params;
|
||||
|
||||
if (!await settingService.isRegister(c)) {
|
||||
throw new BizError('注册功能已关闭');
|
||||
}
|
||||
|
||||
if (!verifyUtils.isEmail(email)) {
|
||||
throw new BizError('非法邮箱');
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
throw new BizError('密码必须大于6位');
|
||||
}
|
||||
|
||||
if (!c.env.domain.includes(emailUtils.getDomain(email))) {
|
||||
throw new BizError('非法邮箱域名');
|
||||
}
|
||||
|
||||
const userRow = await userService.selectByEmailIncludeDel(c, email);
|
||||
|
||||
if (userRow && userRow.isDel === isDel.DELETE) {
|
||||
throw new BizError('该邮箱已被注销');
|
||||
}
|
||||
|
||||
if (userRow) {
|
||||
throw new BizError('该邮箱已被注册');
|
||||
}
|
||||
|
||||
if (await settingService.isRegisterVerify(c)) {
|
||||
await turnstileService.verify(c,token)
|
||||
}
|
||||
|
||||
const { salt, hash } = await saltHashUtils.hashPassword(password);
|
||||
|
||||
const userId = await userService.insert(c, { email, password: hash, salt, type: userConst.type.COMMON });
|
||||
await accountService.insert(c, { userId: userId, email });
|
||||
},
|
||||
|
||||
async login(c, params) {
|
||||
|
||||
const { email, password } = params;
|
||||
|
||||
if (!email || !password) {
|
||||
throw new BizError('邮箱和密码不能为空');
|
||||
}
|
||||
|
||||
const userRow = await userService.selectByEmail(c, email);
|
||||
|
||||
if (!userRow) {
|
||||
throw new BizError('该邮箱不存在');
|
||||
}
|
||||
|
||||
if (!await cryptoUtils.verifyPassword(password, userRow.salt, userRow.password)) {
|
||||
throw new BizError('密码输入错误');
|
||||
}
|
||||
|
||||
const uuid = uuidv4();
|
||||
const jwt = await JwtUtils.generateToken(c,{ userId: userRow.userId, token: uuid });
|
||||
|
||||
let authInfo = await c.env.kv.get(KvConst.AUTH_INFO + userRow.userId, { type: 'json' });
|
||||
|
||||
if (authInfo) {
|
||||
|
||||
authInfo.tokens.push(uuid);
|
||||
|
||||
} else {
|
||||
|
||||
authInfo = {
|
||||
tokens: [],
|
||||
user: userRow,
|
||||
refreshTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
authInfo.tokens.push(uuid);
|
||||
|
||||
}
|
||||
|
||||
await c.env.kv.put(KvConst.AUTH_INFO + userRow.userId, JSON.stringify(authInfo), { expirationTtl: constant.TOKEN_EXPIRE });
|
||||
return jwt;
|
||||
},
|
||||
|
||||
async logout(c, userId) {
|
||||
const token = await userContext.getToken(c);
|
||||
const authInfo = await c.env.kv.get(KvConst.AUTH_INFO + userId, { type: 'json' });
|
||||
const index = authInfo.tokens.findIndex(item => item === token);
|
||||
authInfo.tokens.splice(index, 1);
|
||||
await c.env.kv.put(KvConst.AUTH_INFO + userId, JSON.stringify(authInfo));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default loginService;
|
||||
11
mail-worker/src/service/r2-service.js
Normal file
11
mail-worker/src/service/r2-service.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const r2Service = {
|
||||
async putObj(c, key, content, metadata) {
|
||||
const body = typeof content === 'string'
|
||||
? new TextEncoder().encode(content)
|
||||
: content;
|
||||
await c.env.r2.put(key, body, {
|
||||
httpMetadata: {...metadata}
|
||||
});
|
||||
}
|
||||
};
|
||||
export default r2Service;
|
||||
54
mail-worker/src/service/setting-service.js
Normal file
54
mail-worker/src/service/setting-service.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import KvConst from '../const/kv-const';
|
||||
import setting from '../entity/setting';
|
||||
import orm from '../entity/orm';
|
||||
import { settingConst } from '../const/entity-const';
|
||||
|
||||
const settingService = {
|
||||
|
||||
async refresh(c) {
|
||||
const settingRow = await orm(c).select().from(setting).get();
|
||||
await c.env.kv.put(KvConst.SETTING, JSON.stringify(settingRow));
|
||||
},
|
||||
|
||||
async query(c) {
|
||||
const setting = await c.env.kv.get(KvConst.SETTING, { type: 'json' });
|
||||
let domainList = c.env.domain;
|
||||
domainList = domainList.map(item => '@' + item);
|
||||
setting.domainList = domainList;
|
||||
setting.siteKey = c.env.site_key;
|
||||
setting.r2Domain = c.env.r2_domain;
|
||||
return setting;
|
||||
},
|
||||
|
||||
async set(c, params) {
|
||||
await orm(c).update(setting).set({ ...params }).returning().get();
|
||||
await this.refresh(c);
|
||||
},
|
||||
|
||||
async isRegister(c) {
|
||||
const { register } = await this.query(c);
|
||||
return register === settingConst.register.OPEN;
|
||||
},
|
||||
|
||||
async isReceive(c) {
|
||||
const { receive } = await this.query(c);
|
||||
return receive === settingConst.receive.OPEN;
|
||||
},
|
||||
|
||||
async isAddEmail(c) {
|
||||
const { addEmail, manyEmail } = await this.query(c);
|
||||
return addEmail === settingConst.addEmail.OPEN && manyEmail === settingConst.manyEmail.OPEN;
|
||||
},
|
||||
|
||||
async isRegisterVerify(c) {
|
||||
const { registerVerify } = await this.query(c);
|
||||
return registerVerify === settingConst.registerVerify.OPEN;
|
||||
},
|
||||
|
||||
async isAddEmailVerify(c) {
|
||||
const { addEmailVerify } = await this.query(c);
|
||||
return addEmailVerify === settingConst.addEmailVerify.OPEN;
|
||||
}
|
||||
};
|
||||
|
||||
export default settingService;
|
||||
69
mail-worker/src/service/star-service.js
Normal file
69
mail-worker/src/service/star-service.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import orm from '../entity/orm';
|
||||
import { star } from '../entity/star';
|
||||
import emailService from './email-service';
|
||||
import BizError from '../error/biz-error';
|
||||
import { and, desc, eq, lt, sql } from 'drizzle-orm';
|
||||
import email from '../entity/email';
|
||||
import { isDel } from '../const/entity-const';
|
||||
|
||||
const startService = {
|
||||
|
||||
async add(c, params, userId) {
|
||||
const { emailId } = params;
|
||||
const email = await emailService.selectById(c, emailId);
|
||||
if (!email) {
|
||||
throw new BizError('星标的邮件不存在');
|
||||
}
|
||||
if (!email.userId === userId) {
|
||||
throw new BizError('星标的邮件非当前用户所有');
|
||||
}
|
||||
const exist = await orm(c).select().from(star).where(
|
||||
and(
|
||||
eq(star.userId, userId),
|
||||
eq(star.emailId, emailId)))
|
||||
.get()
|
||||
|
||||
if (exist) {
|
||||
return
|
||||
}
|
||||
|
||||
await orm(c).insert(star).values({ userId, emailId }).run();
|
||||
},
|
||||
|
||||
async cancel(c, params, userId) {
|
||||
const { emailId } = params;
|
||||
await orm(c).delete(star).where(
|
||||
and(
|
||||
eq(star.userId, userId),
|
||||
eq(star.emailId, emailId)))
|
||||
.run();
|
||||
},
|
||||
|
||||
async list(c, params, userId) {
|
||||
let { emailId, size } = params;
|
||||
emailId = Number(emailId);
|
||||
size = Number(size);
|
||||
|
||||
if (!emailId) {
|
||||
emailId = 9999999999;
|
||||
}
|
||||
|
||||
const list = await orm(c).select({
|
||||
isStar: sql`1`.as('isStar'),
|
||||
starId: star.starId
|
||||
, ...email
|
||||
}).from(star)
|
||||
.leftJoin(email, eq(email.emailId, star.emailId))
|
||||
.where(
|
||||
and(
|
||||
eq(star.userId, userId),
|
||||
eq(email.isDel, isDel.NORMAL),
|
||||
lt(star.emailId, emailId)))
|
||||
.orderBy(desc(star.emailId))
|
||||
.limit(size)
|
||||
.all();
|
||||
return { list };
|
||||
}
|
||||
};
|
||||
|
||||
export default startService;
|
||||
30
mail-worker/src/service/turnstile-service.js
Normal file
30
mail-worker/src/service/turnstile-service.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import BizError from '../error/biz-error';
|
||||
|
||||
const turnstileService = {
|
||||
|
||||
async verify(c, token) {
|
||||
|
||||
if (!token) {
|
||||
throw new BizError('验证token不能为空');
|
||||
}
|
||||
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
secret: c.env.secret_key,
|
||||
response: token,
|
||||
remoteip: c.req.header('cf-connecting-ip')
|
||||
})
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
console.log(result)
|
||||
if (!result.success) {
|
||||
throw new BizError('人机验证失败,请重试',400)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default turnstileService;
|
||||
67
mail-worker/src/service/user-service.js
Normal file
67
mail-worker/src/service/user-service.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import BizError from '../error/biz-error';
|
||||
import accountService from './account-service';
|
||||
import orm from '../entity/orm';
|
||||
import user from '../entity/user';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { isDel } from '../const/entity-const';
|
||||
import kvConst from '../const/kv-const';
|
||||
import cryptoUtils from '../utils/crypto-utils';
|
||||
|
||||
const userService = {
|
||||
|
||||
async loginUserInfo(c, userId) {
|
||||
let user = await userService.selectById(c, userId);
|
||||
let account = await accountService.selectByEmailIncludeDel(c, user.email);
|
||||
delete user.password;
|
||||
delete user.salt;
|
||||
user.accountId = account.accountId;
|
||||
user.type = c.env.admin === user.email ? 0 : 1;
|
||||
return user;
|
||||
},
|
||||
|
||||
async resetPassword(c, params, userId) {
|
||||
|
||||
const { password } = params;
|
||||
|
||||
if (password < 6) {
|
||||
throw new BizError('密码不能小于6位');
|
||||
}
|
||||
const { salt, hash } = await cryptoUtils.hashPassword(password);
|
||||
await orm(c).update(user).set({ password: hash, salt: salt }).where(eq(user.userId, userId)).run();
|
||||
},
|
||||
|
||||
selectByEmail(c, email) {
|
||||
return orm(c).select().from(user).where(
|
||||
and(
|
||||
eq(user.email, email),
|
||||
eq(user.isDel, isDel.NORMAL)))
|
||||
.get();
|
||||
},
|
||||
|
||||
async insert(c, params) {
|
||||
const { userId } = await orm(c).insert(user).values({ ...params }).returning().get();
|
||||
return userId;
|
||||
},
|
||||
|
||||
selectByEmailIncludeDel(c, email) {
|
||||
return orm(c).select().from(user).where(eq(user.email, email)).get();
|
||||
},
|
||||
|
||||
selectById(c, userId) {
|
||||
return orm(c).select().from(user).where(
|
||||
and(
|
||||
eq(user.userId, userId),
|
||||
eq(user.isDel, isDel.NORMAL)))
|
||||
.get();
|
||||
},
|
||||
|
||||
async delete(c, userId) {
|
||||
await orm(c).update(user).set({ isDel: isDel.DELETE }).where(eq(user.userId, userId)).run();
|
||||
await Promise.all([
|
||||
c.env.kv.delete(kvConst.AUTH_INFO + userId),
|
||||
accountService.removeByUserId(c, userId)
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
export default userService;
|
||||
Reference in New Issue
Block a user