import Collection from "@discordjs/collection";
import bcrypt from "bcrypt";
import User from "./user";
import { SnowflakeString } from "../types/snowflake";
import Token from "./token";
import Snowflake from "./snowflake";
import { UserType } from "../types/basicUser";
import Message from "./message";
import File from "./file";
import ModQueueItem from "./modQueueItem";
import { BasicAnimation } from "./animation";
import Comment from "./comment";
import { ModQueueItemType } from "../types/modQueueItem";
import { RequestContext, RequestContextWithClient } from "./requestContext";
import { UserRelationType } from "../types/userRelation";
import UserRelation from "./userRelation";
import { prisma } from "../../prisma";
/**
* Holds data about currently logged in user
*/
class UserClient {
ctx: RequestContextWithClient;
token: Token;
user: User;
/**
* Creates new userclient
* @param {object} settings
*/
constructor({
ctx,
token,
user
}: {
ctx: RequestContext,
token: Token,
user: User
}) {
if(!ctx.client) ctx.client = this;
this.user = user;
if(!ctx.hasClient()) throw new Error("Context has no client");
this.ctx = ctx;
this.token = token;
}
/**
* Updates last login time
* @return {Promise<void>}
*/
async updateLastLogin(): Promise<void> {
await prisma.user.update({
where: {
snowflake: this.user.id.toBigInt()
},
data: {
lastLogedIn: new Date()
}
});
this.user.lastLogedIn = new Date();
}
/**
* Tries to login with given password
* @param {User} user
* @param {string} password
*/
static login(user: User, password: string): Promise<UserClient>;
/**
* Tries to login with given token
* @param {RequestContext} ctx
* @param {IToken} token
*/
static login(ctx: RequestContext, token: Token | string): Promise<UserClient>;
/**
* Tries to login with given details
* @param {User} user
* @param {Token | string} auth Token or string
* @return {Promise<UserClient>}
*/
static login(user: User | RequestContext, auth: Token | string): Promise<UserClient> {
if(typeof auth === "string" && user instanceof User) {
return this.loginWithPassword(user, auth);
} else if(!(user instanceof User)) {
return this.loginWithToken(user, auth);
}
throw Error("Unknown overload");
}
/**
* Tries to login with token
* @param {RequestContext} ctx
* @param {token} token
* @return {Promise<UserClient>}
*/
static async loginWithToken(ctx: RequestContext, token: Token | string): Promise<UserClient> {
var t = (typeof token === "string" ? token : token.token);
const tokenData = await prisma.token.findFirstOrThrow({
where: {
token: t
},
include: {
user: true
}
});
const user = User.fromDatabase(tokenData.user, ctx);
const uc = new UserClient({
ctx,
token: Token.fromDatabase(tokenData, ctx),
user
});
await uc.updateLastLogin();
return uc;
}
/**
* Tries to login with password
* @param {User} user
* @param {string} password
* @return {Promise<UserClient>}
*/
static async loginWithPassword(user: User, password: string): Promise<UserClient> {
if(!await bcrypt.compare(password, user.password)) {
throw new Error("bad_password");
}
const uc = new UserClient({
user,
token: new Token({
id: Snowflake.newSnowflake(),
token: null,
created: new Date,
long: false,
ctx: user.ctx
}),
ctx: user.ctx
});
await uc.updateLastLogin();
await prisma.token.create({
data: {
snowflake: uc.token.id.toBigInt(),
token: uc.token.token,
long: uc.token.long,
user: {
connect: {
snowflake: uc.user.id.toBigInt()
}
}
}
});
return uc;
}
/**
* Registers user
* @param {User} user
* @return {Promise<UserClient>}
*/
static async register(user: User): Promise<UserClient> {
const data = await prisma.user.findFirst({
where: {
name: user.name,
type: {
not: UserType.DELETED
}
}
});
if(data) throw new Error("user_exist");
await prisma.user.create({
data: {
snowflake: user.id.toBigInt(),
name: user.name,
email: user.email || null,
avatar: user.avatarId && {
connect: {
snowflake: user.avatarId.toBigInt()
}
},
description: user.description,
type: user.type,
password: user.password,
auth: user.auth,
language: {
connect: {
id: user.language
}
},
lastLogedIn: new Date
}
});
const uc = new UserClient({
user,
ctx: user.ctx,
token: new Token({
id: Snowflake.newSnowflake(),
token: null,
created: new Date,
long: false,
ctx: user.ctx
})
});
await prisma.token.create({
data: {
snowflake: uc.token.id.toBigInt(),
token: uc.token.token,
long: uc.token.long,
user: {
connect: {
snowflake: uc.user.id.toBigInt()
}
}
}
});
return uc;
}
/**
* Gets received messages
* @param {number} max Max items per page
* @param {number} page Current page
* @return {Promise<Collection<SnowflakeString, Message>>}
*/
async getReceivedMessages(max: number, page: number):
Promise<Collection<SnowflakeString, Message>> {
const data = await prisma.message.findMany({
where: {
targetId: this.user.id.toBigInt()
},
orderBy: {
snowflake: "desc"
},
skip: max*page,
take: max,
include: {
author: true
}
});
const col = new Collection<SnowflakeString, Message>();
for(const message of data) {
col.set(message.snowflake.toString(), await Message.fromDatabase(message, this.ctx));
}
return col;
}
/**
* Returns received message count
* @return {Promise<number>}
*/
async getReceivedMessageCount(): Promise<number> {
return await prisma.message.count({
where: {
targetId: this.user.id.toBigInt()
}
});
}
/**
* Returns message count
* @param {bigint | User | SnowflakeString | Snowflake} uid
* @return {Promise<number>}
*/
async getMessageCount(uid: bigint | User | SnowflakeString | Snowflake):
Promise<number> {
var id: bigint;
if(uid instanceof Snowflake) {
id = uid.toBigInt();
} else if(typeof uid === "bigint") {
id = uid;
} else if(uid instanceof User) {
id = uid.id.toBigInt();
} else {
id = BigInt(uid);
}
return await prisma.message.count({
where: {
OR: [
{
authorId: id,
targetId: this.user.id.toBigInt()
}, {
authorId: this.user.id.toBigInt(),
targetId: id
}
]
}
});
}
/**
* Gets conversation with user
* @param {number | User | SnowflakeString | Snowflake} uid
* @param {number} max Max items per page
* @param {number} page Current page
* @return {Promise<Collection<SnowflakeString, Message>>}
*/
async getMessages(uid: number | User | SnowflakeString | Snowflake, max: number, page: number):
Promise<Collection<SnowflakeString, Message>> {
var id: bigint;
if(uid instanceof Snowflake) {
id = uid.toBigInt();
} else if(typeof uid === "bigint") {
id = uid;
} else if(uid instanceof User) {
id = uid.id.toBigInt();
} else {
id = BigInt(uid);
}
const data = await prisma.message.findMany({
where: {
OR: [
{
authorId: id,
targetId: this.user.id.toBigInt()
}, {
authorId: this.user.id.toBigInt(),
targetId: id
}
]
},
orderBy: {
snowflake: "desc"
},
skip: max*page,
take: max
});
const col = new Collection<SnowflakeString, Message>();
for(const message of data) {
col.set(message.snowflake.toString(), await Message.fromDatabase(message, this.ctx));
}
return col;
}
/**
* Sends new message
* @param {Message} message Message to send
* @return {Promise<Message>}
*/
async sendMessage(message: Message): Promise<Message> {
await prisma.message.create({
data: {
snowflake: message.id.toBigInt(),
author: {
connect: {
snowflake: message.author.id.toBigInt()
}
},
target: {
connect: {
snowflake: message.target.id.toBigInt()
}
},
content: message.content
}
});
return message;
}
/**
* Gets blocked users
* @return {Promise<UserRelation[]>}
*/
async getBlockedUsers(): Promise<UserRelation[]> {
const data = await prisma.userRelation.findMany({
where: {
authorId: this.user.id.toBigInt(),
type: UserRelationType.BLOCKING
},
include: {
target: true
}
});
return await Promise.all(data.map(async (rel) => UserRelation.fromDatabase(rel, this.ctx)));
}
/**
* Fetches items in mod queue
* @param {number} page
* @param {number} size
* @return {Promise<ModQueueItem[]>}
*/
async getModQueue(page: number, size: number): Promise<ModQueueItem[]> {
if(this.user.type < UserType.MODERATOR) throw new Error("Missing permissions");
const data = await prisma.modQueue.groupBy({
by: ["resourceId", "type", "reason", "userId"],
_count: {
snowflake: true
},
orderBy: {
resourceId: "desc"
},
skip: page * size,
take: size
});
return await Promise.all(data.map((item) => ModQueueItem.fromAggregated({
count: item._count.snowflake,
reason: item.reason,
resourceId: item.resourceId,
type: item.type,
userId: item.userId
}, this.ctx)));
}
/**
* Gets the reports of selected item
* @param {any} item to get reports of
* @param {number} page
* @param {number} size
* @return {Promise<Collection<SnowflakeString, ModQueueItem>>}
*/
async getReportsOf(item: BasicAnimation | User | Comment | Message | File,
page: number, size: number):
Promise<Collection<SnowflakeString, ModQueueItem>> {
if(this.user.type < UserType.MODERATOR) throw new Error("Missing permissions");
// return new Promise((resolve, reject) => {
let type: ModQueueItemType;
if(item instanceof BasicAnimation) type = ModQueueItemType.ANIMATION;
else if(item instanceof User) type = ModQueueItemType.USER;
else if(item instanceof Comment) type = ModQueueItemType.ANIMATION_COMMENT;
else if(item instanceof Message) type = ModQueueItemType.USER_MESSAGE;
else if(item instanceof File) type = ModQueueItemType.FILE;
else throw new TypeError("Unknown type");
var resource: SnowflakeString = item.id.toString();
const reports = await prisma.modQueue.findMany({
where: {
resourceId: BigInt(resource),
type
},
include: {
reporter: true
},
skip: page * size,
take: size
});
const out = new Collection<SnowflakeString, ModQueueItem>();
for(const item of reports) {
out.set(item.snowflake.toString(), await ModQueueItem.fromDatabase(item, this.ctx));
}
return out;
}
/**
* Returns the length of mod queue
* @param {any} [item] to get reports of
* @return {Promise<number>}
*/
async getModQueueLength(item?: BasicAnimation | User | Comment | Message | File):
Promise<number> {
var type: ModQueueItemType | undefined;
if(item instanceof BasicAnimation) type = ModQueueItemType.ANIMATION;
else if(item instanceof User) type = ModQueueItemType.USER;
else if(item instanceof Comment) type = ModQueueItemType.ANIMATION_COMMENT;
else if(item instanceof Message) type = ModQueueItemType.USER_MESSAGE;
else if(item instanceof File) type = ModQueueItemType.FILE;
else if(typeof item !== "undefined") throw new TypeError("Unknown type");
var id: SnowflakeString | undefined = item?.id.toString();
return prisma.modQueue.count({
where: {
type,
resourceId: id ? BigInt(id) : undefined
}
});
}
/**
* Tries to find given report
* @param {SnowflakeString} user
* @param {ModQueueItemType} type
* @param {SnowflakeString} resource
* @return {Promise<ModQueueItem | void>}
*/
async findReport(user: SnowflakeString, type: ModQueueItemType, resource: SnowflakeString):
Promise<ModQueueItem | void> {
const report = await prisma.modQueue.findFirst({
where: {
reporter: {
snowflake: BigInt(user)
},
type,
resourceId: BigInt(resource)
}
});
if(!report) return;
return ModQueueItem.fromDatabase(report, this.ctx);
}
/**
* Reports an item
* @param {any} item to report
* @param {string} reason of report
* @return {Promise<ModQueueItem>}
*/
async report(item: BasicAnimation | User | Comment | Message | File, reason: string):
Promise<ModQueueItem> {
var type: ModQueueItemType;
if(item instanceof BasicAnimation) type = ModQueueItemType.ANIMATION;
else if(item instanceof User) type = ModQueueItemType.USER;
else if(item instanceof Comment) type = ModQueueItemType.ANIMATION_COMMENT;
else if(item instanceof Message) type = ModQueueItemType.USER_MESSAGE;
else if(item instanceof File) type = ModQueueItemType.FILE;
else throw new TypeError("Unknown type");
if(await this.findReport(this.user.id.toString(), type, item.id.toString())) {
throw new Error("Already reported");
}
let user: User;
switch(type) {
case ModQueueItemType.ANIMATION: user = (item as BasicAnimation).author;
break;
case ModQueueItemType.ANIMATION_COMMENT: user = (item as Comment).user;
break;
case ModQueueItemType.USER_MESSAGE: user = (item as Message).author;
break;
case ModQueueItemType.USER: user = item as User;
break;
case ModQueueItemType.FILE: user = (item as File).author;
break;
default: throw new Error("invalid type");
}
const data = await prisma.modQueue.upsert({
create: {
snowflake: Snowflake.newSnowflake().toBigInt(),
type,
reason,
reporter: {
connect: {
snowflake: this.user.id.toBigInt()
}
},
user: {
connect: {
snowflake: user.id.toBigInt()
}
},
resourceId: item.id.toBigInt()
},
where: {
resourceId_reporterId: {
reporterId: this.user.id.toBigInt(),
resourceId: item.id.toBigInt()
}
},
update: {
reason
}
});
return ModQueueItem.fromDatabase(data, this.ctx);
}
/**
* Updates user data (synchronizes DB with class)
* @return {Promise<void>}
*/
async update(): Promise<void> {
await prisma.user.update({
where: {
snowflake: this.user.id.toBigInt()
},
data: {
email: this.user.email,
avatarId: this.user.avatar?.id?.toBigInt() || null,
description: this.user.description,
type: this.user.type,
password: this.user.password,
auth: this.user.auth,
languageId: this.user.language
}
});
}
/**
* Deletes user data (deletes from DB)
* @return {Promise<void>}
*/
delete(): Promise<void> {
this.user.type = UserType.DELETED;
this.user.email = "";
this.user.name = "[DELETED]";
this.user.description = "";
return this.update();
}
/**
* Creates a JSON-compatible object
* @return {Object}
*/
toJSON() {
return {
user: this.user.toJSON(),
token: this.token.token
};
}
}
export default UserClient;
Source