优化
This commit is contained in:
47
server/models/user/dmlist.ts
Normal file
47
server/models/user/dmlist.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
getModelForClass,
|
||||
prop,
|
||||
DocumentType,
|
||||
Ref,
|
||||
modelOptions,
|
||||
} from '@typegoose/typegoose';
|
||||
import { Base, FindOrCreate } from '@typegoose/typegoose/lib/defaultClasses';
|
||||
import { Converse } from '../chat/converse';
|
||||
import { User } from './user';
|
||||
import findorcreate from 'mongoose-findorcreate';
|
||||
import { plugin } from '@typegoose/typegoose';
|
||||
import type { Types } from 'mongoose';
|
||||
|
||||
/**
|
||||
* 用户私信列表管理
|
||||
*/
|
||||
|
||||
@plugin(findorcreate)
|
||||
@modelOptions({
|
||||
schemaOptions: {
|
||||
collection: 'userdmlist',
|
||||
},
|
||||
})
|
||||
export class UserDMList extends FindOrCreate implements Base {
|
||||
_id: Types.ObjectId;
|
||||
id: string;
|
||||
|
||||
@prop({
|
||||
ref: () => User,
|
||||
index: true,
|
||||
})
|
||||
userId: Ref<User>;
|
||||
|
||||
@prop({
|
||||
ref: () => Converse,
|
||||
})
|
||||
converseIds: Ref<Converse>[];
|
||||
}
|
||||
|
||||
export type UserDMListDocument = DocumentType<UserDMList>;
|
||||
|
||||
const model = getModelForClass(UserDMList);
|
||||
|
||||
export type UserDMListModel = typeof model;
|
||||
|
||||
export default model;
|
||||
67
server/models/user/friend.ts
Normal file
67
server/models/user/friend.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
getModelForClass,
|
||||
prop,
|
||||
DocumentType,
|
||||
Ref,
|
||||
plugin,
|
||||
ReturnModelType,
|
||||
} from '@typegoose/typegoose';
|
||||
import { Base, FindOrCreate } from '@typegoose/typegoose/lib/defaultClasses';
|
||||
import { User } from './user';
|
||||
import findorcreate from 'mongoose-findorcreate';
|
||||
import type { Types } from 'mongoose';
|
||||
|
||||
/**
|
||||
* 好友请求
|
||||
* 单向好友结构
|
||||
*/
|
||||
@plugin(findorcreate)
|
||||
export class Friend extends FindOrCreate implements Base {
|
||||
_id: Types.ObjectId;
|
||||
id: string;
|
||||
|
||||
@prop({
|
||||
ref: () => User,
|
||||
index: true,
|
||||
})
|
||||
from: Ref<User>;
|
||||
|
||||
@prop({
|
||||
ref: () => User,
|
||||
})
|
||||
to: Ref<User>;
|
||||
|
||||
/**
|
||||
* 好友昵称, 覆盖用户自己的昵称
|
||||
*/
|
||||
@prop()
|
||||
nickname?: string;
|
||||
|
||||
@prop()
|
||||
createdAt: Date;
|
||||
|
||||
static async buildFriendRelation(
|
||||
this: ReturnModelType<FriendModel>,
|
||||
user1: string,
|
||||
user2: string
|
||||
) {
|
||||
await Promise.all([
|
||||
this.findOrCreate({
|
||||
from: user1,
|
||||
to: user2,
|
||||
}),
|
||||
this.findOrCreate({
|
||||
from: user2,
|
||||
to: user1,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export type FriendDocument = DocumentType<Friend>;
|
||||
|
||||
const model = getModelForClass(Friend);
|
||||
|
||||
export type FriendModel = typeof model;
|
||||
|
||||
export default model;
|
||||
36
server/models/user/friendRequest.ts
Normal file
36
server/models/user/friendRequest.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
getModelForClass,
|
||||
prop,
|
||||
DocumentType,
|
||||
Ref,
|
||||
} from '@typegoose/typegoose';
|
||||
import type { Base } from '@typegoose/typegoose/lib/defaultClasses';
|
||||
import type { Types } from 'mongoose';
|
||||
import { User } from './user';
|
||||
|
||||
/**
|
||||
* 好友请求
|
||||
*/
|
||||
|
||||
export class FriendRequest implements Base {
|
||||
_id: Types.ObjectId;
|
||||
id: string;
|
||||
|
||||
@prop({
|
||||
ref: () => User,
|
||||
index: true,
|
||||
})
|
||||
from: Ref<User>;
|
||||
|
||||
@prop({
|
||||
ref: () => User,
|
||||
})
|
||||
to: Ref<User>;
|
||||
|
||||
@prop()
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type FriendRequestDocument = DocumentType<FriendRequest>;
|
||||
|
||||
export default getModelForClass(FriendRequest);
|
||||
191
server/models/user/mail.ts
Normal file
191
server/models/user/mail.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
getModelForClass,
|
||||
prop,
|
||||
DocumentType,
|
||||
Ref,
|
||||
modelOptions,
|
||||
Severity,
|
||||
ReturnModelType,
|
||||
} from '@typegoose/typegoose';
|
||||
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses';
|
||||
import type { Types } from 'mongoose';
|
||||
import { User } from './user';
|
||||
import nodemailer, { Transporter, SendMailOptions } from 'nodemailer';
|
||||
import { parseConnectionUrl } from 'nodemailer/lib/shared';
|
||||
import { config } from 'tailchat-server-sdk';
|
||||
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||
|
||||
/**
|
||||
* 将地址格式化
|
||||
*/
|
||||
function stringifyAddress(address: SendMailOptions['to']): string {
|
||||
if (Array.isArray(address)) {
|
||||
return address.map((a) => stringifyAddress(a)).join(',');
|
||||
}
|
||||
|
||||
if (typeof address === 'string') {
|
||||
return address;
|
||||
} else if (address === undefined) {
|
||||
return '';
|
||||
} else if (typeof address === 'object') {
|
||||
return `"${address.name}" ${address.address}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getSMTPConnectionOptions(): SMTPConnection.Options | null {
|
||||
if (config.smtp.connectionUrl) {
|
||||
return parseConnectionUrl(config.smtp.connectionUrl);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@modelOptions({
|
||||
options: {
|
||||
allowMixed: Severity.ALLOW,
|
||||
},
|
||||
})
|
||||
export class Mail extends TimeStamps implements Base {
|
||||
_id: Types.ObjectId;
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* 发件人邮箱
|
||||
*/
|
||||
@prop()
|
||||
from: string;
|
||||
|
||||
/**
|
||||
* 收件人邮箱
|
||||
*/
|
||||
@prop()
|
||||
to: string;
|
||||
|
||||
/**
|
||||
* 邮件主题
|
||||
*/
|
||||
@prop()
|
||||
subject: string;
|
||||
|
||||
/**
|
||||
* 邮件内容
|
||||
*/
|
||||
@prop()
|
||||
body: string;
|
||||
|
||||
@prop()
|
||||
host?: string;
|
||||
|
||||
@prop()
|
||||
port?: string;
|
||||
|
||||
@prop()
|
||||
secure?: boolean;
|
||||
|
||||
@prop()
|
||||
is_success: boolean;
|
||||
|
||||
@prop()
|
||||
data?: any;
|
||||
|
||||
@prop()
|
||||
error?: string;
|
||||
|
||||
/**
|
||||
* 创建邮件发送实例
|
||||
*/
|
||||
static createMailerTransporter(): Transporter | null {
|
||||
const options = getSMTPConnectionOptions();
|
||||
|
||||
if (options) {
|
||||
const transporter = nodemailer.createTransport(options);
|
||||
|
||||
return transporter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查邮件服务是否可用
|
||||
*/
|
||||
static async verifyMailService(): Promise<boolean> {
|
||||
try {
|
||||
const transporter = Mail.createMailerTransporter();
|
||||
|
||||
if (!transporter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const verify = await transporter.verify();
|
||||
return verify;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
*/
|
||||
static async sendMail(
|
||||
this: ReturnModelType<typeof Mail>,
|
||||
mailOptions: SendMailOptions
|
||||
) {
|
||||
try {
|
||||
const transporter = Mail.createMailerTransporter();
|
||||
if (!transporter) {
|
||||
throw new Error('Mail Transporter is null');
|
||||
}
|
||||
|
||||
const options = {
|
||||
from: config.smtp.senderName,
|
||||
...mailOptions,
|
||||
};
|
||||
|
||||
const smtpOptions = getSMTPConnectionOptions();
|
||||
try {
|
||||
const info = await transporter.sendMail(options);
|
||||
|
||||
await this.create({
|
||||
from: stringifyAddress(options.from),
|
||||
to: stringifyAddress(options.to),
|
||||
subject: options.subject,
|
||||
body: options.html,
|
||||
host: smtpOptions.host,
|
||||
port: smtpOptions.port,
|
||||
secure: smtpOptions.secure,
|
||||
is_success: true,
|
||||
data: info,
|
||||
});
|
||||
|
||||
return info;
|
||||
} catch (err) {
|
||||
this.create({
|
||||
from: stringifyAddress(options.from),
|
||||
to: stringifyAddress(options.to),
|
||||
subject: options.subject,
|
||||
body: options.html,
|
||||
host: smtpOptions.host,
|
||||
port: smtpOptions.port,
|
||||
secure: smtpOptions.secure,
|
||||
is_success: false,
|
||||
error: String(err),
|
||||
});
|
||||
|
||||
throw err;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type MailDocument = DocumentType<Mail>;
|
||||
|
||||
const model = getModelForClass(Mail);
|
||||
|
||||
export type MailModel = typeof model;
|
||||
|
||||
export default model;
|
||||
191
server/models/user/user.ts
Normal file
191
server/models/user/user.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
getModelForClass,
|
||||
prop,
|
||||
DocumentType,
|
||||
ReturnModelType,
|
||||
modelOptions,
|
||||
Severity,
|
||||
index,
|
||||
} from '@typegoose/typegoose';
|
||||
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses';
|
||||
import type { Types } from 'mongoose';
|
||||
import { UserType, userType } from 'tailchat-server-sdk';
|
||||
|
||||
type BaseUserInfo = Pick<User, 'nickname' | 'discriminator' | 'avatar'>;
|
||||
|
||||
/**
|
||||
* 用户设置
|
||||
*/
|
||||
export interface UserSettings {
|
||||
/**
|
||||
* 消息列表虚拟化
|
||||
*/
|
||||
messageListVirtualization?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface UserLoginRes extends User {
|
||||
token: string;
|
||||
}
|
||||
|
||||
@modelOptions({
|
||||
options: {
|
||||
allowMixed: Severity.ALLOW,
|
||||
},
|
||||
})
|
||||
@index({ avatar: 1 })
|
||||
export class User extends TimeStamps implements Base {
|
||||
_id: Types.ObjectId;
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* 用户名 不可被修改
|
||||
* 与email必有一个
|
||||
*/
|
||||
@prop()
|
||||
username?: string;
|
||||
|
||||
/**
|
||||
* 邮箱 不可被修改
|
||||
* 必填
|
||||
*/
|
||||
@prop({
|
||||
index: true,
|
||||
unique: true,
|
||||
})
|
||||
email: string;
|
||||
|
||||
@prop()
|
||||
password!: string;
|
||||
|
||||
/**
|
||||
* 可以被修改的显示名
|
||||
*/
|
||||
@prop({
|
||||
trim: true,
|
||||
maxlength: 20,
|
||||
})
|
||||
nickname!: string;
|
||||
|
||||
/**
|
||||
* 识别器, 跟username构成全局唯一的用户名
|
||||
* 用于搜索
|
||||
* <username>#<discriminator>
|
||||
*/
|
||||
@prop()
|
||||
discriminator: string;
|
||||
|
||||
/**
|
||||
* 是否为临时用户
|
||||
* @default false
|
||||
*/
|
||||
@prop({
|
||||
default: false,
|
||||
})
|
||||
temporary: boolean;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@prop()
|
||||
avatar?: string;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
@prop({
|
||||
enum: userType,
|
||||
type: () => String,
|
||||
default: 'normalUser',
|
||||
})
|
||||
type: UserType;
|
||||
|
||||
/**
|
||||
* 邮箱是否可用
|
||||
*/
|
||||
@prop({
|
||||
default: false,
|
||||
})
|
||||
emailVerified: boolean;
|
||||
|
||||
/**
|
||||
* 是否被封禁
|
||||
*/
|
||||
@prop({
|
||||
default: false,
|
||||
})
|
||||
banned: boolean;
|
||||
|
||||
/**
|
||||
* 用户的额外信息
|
||||
*/
|
||||
@prop()
|
||||
extra?: object;
|
||||
|
||||
/**
|
||||
* 用户设置
|
||||
*/
|
||||
@prop({
|
||||
default: {},
|
||||
})
|
||||
settings: UserSettings;
|
||||
|
||||
/**
|
||||
* 生成身份识别器
|
||||
* 0001 - 9999
|
||||
*/
|
||||
public static generateDiscriminator(
|
||||
this: ReturnModelType<typeof User>,
|
||||
nickname: string
|
||||
): Promise<string> {
|
||||
let restTimes = 10; // 最多找10次
|
||||
const checkDiscriminator = async () => {
|
||||
const discriminator = String(
|
||||
Math.floor(Math.random() * 9999) + 1
|
||||
).padStart(4, '0');
|
||||
|
||||
const doc = await this.findOne({
|
||||
nickname,
|
||||
discriminator,
|
||||
}).exec();
|
||||
restTimes--;
|
||||
|
||||
if (doc !== null) {
|
||||
// 已存在, 换一个
|
||||
if (restTimes <= 0) {
|
||||
throw new Error('Cannot find space discriminator');
|
||||
}
|
||||
|
||||
return checkDiscriminator();
|
||||
}
|
||||
|
||||
return discriminator;
|
||||
};
|
||||
|
||||
return checkDiscriminator();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户基本信息
|
||||
*/
|
||||
static async getUserBaseInfo(
|
||||
this: ReturnModelType<typeof User>,
|
||||
userId: string
|
||||
): Promise<BaseUserInfo> {
|
||||
const user = await this.findById(String(userId));
|
||||
|
||||
return {
|
||||
nickname: user.nickname,
|
||||
discriminator: user.discriminator,
|
||||
avatar: user.avatar,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type UserDocument = DocumentType<User>;
|
||||
|
||||
const model = getModelForClass(User);
|
||||
|
||||
export type UserModel = typeof model;
|
||||
|
||||
export default model;
|
||||
Reference in New Issue
Block a user