This commit is contained in:
2026-04-25 16:36:34 +08:00
commit db90e7579b
1876 changed files with 189777 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
import type { Ref } from '@typegoose/typegoose';
import type { Converse } from '../../../models/chat/converse';
import type {
UserDMList,
UserDMListDocument,
UserDMListModel,
} from '../../../models/user/dmlist';
import { TcService, TcContext, TcDbService, db } from 'tailchat-server-sdk';
interface UserDMListService
extends TcService,
TcDbService<UserDMListDocument, UserDMListModel> {}
class UserDMListService extends TcService {
get serviceName(): string {
return 'user.dmlist';
}
onInit(): void {
this.registerLocalDb(require('../../../models/user/dmlist').default);
this.registerAction('addConverse', this.addConverse, {
params: {
converseId: 'string',
},
});
this.registerAction('removeConverse', this.removeConverse, {
params: {
converseId: 'string',
},
});
this.registerAction('getAllConverse', this.getAllConverse);
}
async addConverse(ctx: TcContext<{ converseId: string }>) {
const userId = ctx.meta.userId;
const converseId = ctx.params.converseId;
const record = await this.adapter.model.findOrCreate({
userId,
});
const res = await this.adapter.model.findByIdAndUpdate(record.doc._id, {
$addToSet: {
converseIds: new db.Types.ObjectId(converseId),
},
});
return await this.transformDocuments(ctx, {}, res);
}
/**
* 移除会话
*/
async removeConverse(ctx: TcContext<{ converseId: string }>) {
const userId = ctx.meta.userId;
const converseId = ctx.params.converseId;
const { modifiedCount } = await this.adapter.model
.updateOne(
{
userId,
},
{
$pull: {
converseIds: converseId,
},
}
)
.exec();
return { modifiedCount };
}
/**
* 获取所有会话
*/
async getAllConverse(ctx: TcContext): Promise<Ref<Converse>[]> {
const userId = ctx.meta.userId;
const doc = await this.adapter.model.findOne({
userId,
});
const res: UserDMList | null = await this.transformDocuments(ctx, {}, doc);
return res?.converseIds ?? [];
}
}
export default UserDMListService;

View File

@@ -0,0 +1,136 @@
import type {
Friend,
FriendDocument,
FriendModel,
} from '../../../models/user/friend';
import { TcService, TcDbService, TcContext } from 'tailchat-server-sdk';
import { isNil } from 'lodash';
interface FriendService
extends TcService,
TcDbService<FriendDocument, FriendModel> {}
class FriendService extends TcService {
get serviceName(): string {
return 'friend';
}
onInit(): void {
this.registerLocalDb(require('../../../models/user/friend').default);
// this.registerMixin(TcCacheCleaner(['cache.clean.friend']));
this.registerAction('getAllFriends', this.getAllFriends);
this.registerAction('buildFriendRelation', this.buildFriendRelation, {
params: {
user1: 'string',
user2: 'string',
},
});
this.registerAction('removeFriend', this.removeFriend, {
params: {
friendUserId: 'string',
},
});
this.registerAction('checkIsFriend', this.checkIsFriend, {
params: {
targetId: 'string',
},
});
this.registerAction('setFriendNickname', this.setFriendNickname, {
params: {
targetId: 'string',
nickname: 'string',
},
});
}
/**
* 获取所有好友
*/
async getAllFriends(ctx: TcContext<{}>) {
const userId = ctx.meta.userId;
const list = await this.adapter.find({
query: {
from: userId,
},
});
const records: Friend[] = await this.transformDocuments(ctx, {}, list);
const res = records.map((r) => ({
id: r.to,
nickname: r.nickname,
}));
return res;
}
/**
* 构建好友关系
*/
async buildFriendRelation(ctx: TcContext<{ user1: string; user2: string }>) {
const { user1, user2 } = ctx.params;
await this.adapter.model.buildFriendRelation(user1, user2);
this.unicastNotify(ctx, user1, 'add', {
userId: user2,
});
this.unicastNotify(ctx, user2, 'add', {
userId: user1,
});
}
/**
* 移除单项好友关系
*/
async removeFriend(ctx: TcContext<{ friendUserId: string }>) {
const { friendUserId } = ctx.params;
const { userId } = ctx.meta;
await this.adapter.model.findOneAndRemove({
from: userId,
to: friendUserId,
});
}
/**
* 检查对方是否为自己好友
*/
async checkIsFriend(ctx: TcContext<{ targetId: string }>) {
const { targetId } = ctx.params;
const userId = ctx.meta.userId;
const isFriend = await this.adapter.model.exists({
from: userId,
to: targetId,
});
return isFriend;
}
/**
* 设置好友昵称
*/
async setFriendNickname(
ctx: TcContext<{ targetId: string; nickname: string }>
) {
const { targetId, nickname } = ctx.params;
const userId = ctx.meta.userId;
const t = ctx.meta.t;
const res = await this.adapter.model.findOneAndUpdate(
{
from: userId,
to: targetId,
},
{
nickname: nickname,
}
);
if (isNil(res)) {
throw new Error(t('设置昵称失败, 没有找到好友关系信息'));
}
return true;
}
}
export default FriendService;

View File

@@ -0,0 +1,190 @@
import {
TcService,
TcDbService,
TcContext,
Errors,
DataNotFoundError,
NoPermissionError,
config,
} from 'tailchat-server-sdk';
import _ from 'lodash';
import type { FriendRequest } from '../../../models/user/friendRequest';
interface FriendService extends TcService, TcDbService<any> {}
class FriendService extends TcService {
get serviceName(): string {
return 'friend.request';
}
onInit(): void {
this.registerLocalDb(require('../../../models/user/friendRequest').default);
// this.registerMixin(TcCacheCleaner(['cache.clean.friend']));
this.registerAction('add', this.add, {
params: {
to: 'string',
message: [{ type: 'string', optional: true }],
},
});
this.registerAction('allRelated', this.allRelated);
this.registerAction('accept', this.accept, {
params: {
requestId: 'string',
},
});
this.registerAction('deny', this.deny, {
params: {
requestId: 'string',
},
});
this.registerAction('cancel', this.cancel, {
params: {
requestId: 'string',
},
});
}
/**
* 请求添加好友
*/
async add(ctx: TcContext<{ to: string; message?: string }>) {
const from = ctx.meta.userId;
const t = ctx.meta.t;
const { to, message } = ctx.params;
if (config.feature.disableAddFriend === true) {
throw new NoPermissionError(t('管理员禁止添加好友功能'));
}
if (from === to) {
throw new Errors.MoleculerError(t('不能添加自己为好友'));
}
const exist = await this.adapter.findOne({
from,
to,
});
if (exist) {
throw new Errors.MoleculerError(t('不能发送重复的好友请求'));
}
const isFriend = await ctx.call('friend.checkIsFriend', { targetId: to });
if (isFriend) {
throw new Error(t('对方已经是您的好友, 不能再次添加'));
}
const doc = await this.adapter.insert({
from,
to,
message,
});
const request = await this.transformDocuments(ctx, {}, doc);
this.listcastNotify(ctx, [from, to], 'add', request);
return request;
}
/**
* 所有与自己相关的好友请求
*/
async allRelated(ctx: TcContext) {
const userId = ctx.meta.userId;
const doc = await this.adapter.find({
query: {
$or: [{ from: userId }, { to: userId }],
},
});
const list = await await this.transformDocuments(ctx, {}, doc);
return list;
}
/**
* 接受好友请求
*/
async accept(ctx: TcContext<{ requestId: string }>) {
const requestId = ctx.params.requestId;
const request: FriendRequest = await this.adapter.findById(requestId);
if (_.isNil(request)) {
throw new DataNotFoundError('该好友请求未找到');
}
if (ctx.meta.userId !== String(request.to)) {
throw new NoPermissionError();
}
await ctx.call('friend.buildFriendRelation', {
user1: String(request.from),
user2: String(request.to),
});
await this.adapter.removeById(request._id);
this.listcastNotify(
ctx,
[String(request.from), String(request.to)],
'remove',
{
requestId,
}
);
}
/**
* 拒绝好友请求
*/
async deny(ctx: TcContext<{ requestId: string }>) {
const requestId = ctx.params.requestId;
const request: FriendRequest = await this.adapter.findById(requestId);
if (_.isNil(request)) {
throw new DataNotFoundError('该好友请求未找到');
}
if (ctx.meta.userId !== String(request.to)) {
throw new NoPermissionError();
}
await this.adapter.removeById(request._id);
this.listcastNotify(
ctx,
[String(request.from), String(request.to)],
'remove',
{
requestId,
}
);
}
/**
* 取消好友请求
*/
async cancel(ctx: TcContext<{ requestId: string }>) {
const requestId = ctx.params.requestId;
const request: FriendRequest = await this.adapter.findById(requestId);
if (_.isNil(request)) {
throw new DataNotFoundError('该好友请求未找到');
}
if (ctx.meta.userId !== String(request.from)) {
throw new NoPermissionError();
}
await this.adapter.removeById(request._id);
this.listcastNotify(
ctx,
[String(request.from), String(request.to)],
'remove',
{
requestId,
}
);
}
}
export default FriendService;

View File

@@ -0,0 +1,76 @@
import type { MailDocument, MailModel } from '../../../models/user/mail';
import { TcService, TcContext, TcDbService } from 'tailchat-server-sdk';
import ejs from 'ejs';
import path from 'path';
interface MailService extends TcService, TcDbService<MailDocument, MailModel> {}
class MailService extends TcService {
smtpServiceAvailable = false;
get serviceName(): string {
return 'mail';
}
onInit(): void {
this.registerLocalDb(require('../../../models/user/mail').default);
this.registerAction('sendMail', this.sendMail, {
visibility: 'public',
params: {
to: 'string',
subject: 'string',
html: 'string',
},
});
}
onInited() {
this.adapter.model.verifyMailService().then((available) => {
if (available) {
this.logger.info('SMTP 服务可用');
} else {
this.logger.warn('SMTP 服务不可用');
}
this.smtpServiceAvailable = available;
});
}
/**
* 发送邮件
*/
async sendMail(
ctx: TcContext<{
to: string;
subject: string;
html: string;
}>
) {
if (!this.smtpServiceAvailable) {
throw new Error('SMTP 服务不可用');
}
const { to, subject, html } = ctx.params;
const { t } = ctx.meta;
try {
const info = await this.adapter.model.sendMail({
to,
subject,
html: await ejs.renderFile(
path.resolve(__dirname, '../../../views/mail.ejs'),
{
body: html,
}
),
});
this.logger.info('sendMailSuccess:', info);
} catch (err) {
this.logger.error('sendMailFailed:', err);
throw new Error(t('邮件发送失败'));
}
}
}
export default MailService;

File diff suppressed because it is too large Load Diff