import IUser, { UserResolvable } from "../types/user";
import Snowflake from "./snowflake";
import Language, { LanguageCodes } from "../types/language";
import { SnowflakeResolvable, SnowflakeString } from "../types/snowflake";
import { UserType } from "../types/basicUser";
import Collection from "@discordjs/collection";
import Animation, { BasicAnimation } from "./animation";
import Category from "./category";
import Comment from "./comment";
import SystemRelation from "./systemRelation";
import File from "./file";
import Rating from "./rating";
import UserRelation from "./userRelation";
import { RequestContext } from "./requestContext";
import UserFilters from "./userFilters";
import { DBUser, prisma } from "../../prisma";
import { FileState } from "../types/file";
/**
* User data class
*/
class User {
id: Snowflake;
ctx: RequestContext;
avatar?: File;
avatarId?: Snowflake;
name: string;
description: string;
email?: string;
password: string;
language: Language;
auth: string;
type: UserType;
lastLogedIn?: Date;
reported?: boolean;
/**
* User class constructor
* @param {Object} param0 user data
*/
constructor({
id,
ctx,
name = "",
avatar,
avatarId,
description = "",
email = "",
auth = "",
password = "",
language = Language.EN,
type = UserType.MEMBER,
lastLogedIn,
reported
}: {
id?: Snowflake,
ctx: RequestContext,
name?: string,
avatar?: File,
avatarId?: Snowflake,
description?: string,
email?: string,
password?: string,
language?: Language,
auth?: string,
type?: UserType,
lastLogedIn?: Date,
reported?: boolean
}) {
this.id = id || Snowflake.newSnowflake();
this.ctx = ctx;
this.name = name;
this.avatar = avatar;
this.avatarId = avatarId;
this.description = description;
this.email = email;
this.auth = auth;
this.type = type;
this.language = language;
this.password = password;
this.lastLogedIn = lastLogedIn;
this.reported = reported;
}
/**
* Checks if user resolvable is user or not. Used for TS.
* @param {UserResolvable} user
* @return {boolean}
*/
static isUser(user: UserResolvable): user is User | IUser {
return user instanceof User;
}
/**
* Resolves params to string
* @param {UserResolvable} user
* @return {SnowflakeString}
*/
static resolveUserID(user: UserResolvable): SnowflakeString {
if(User.isUser(user)) return user.id.toString();
return user.toString();
}
/**
* Loads the user from database data
*
* @param {DBUser} data
* @param {RequestContext} ctx
*
* @return {User}
*/
static fromDatabase(data: DBUser, ctx: RequestContext) {
if(ctx.users.has(data.snowflake.toString())) {
return ctx.users.get(data.snowflake.toString()) as User;
}
const user = new User({
ctx,
id: new Snowflake(data.snowflake),
name: data.name,
description: data.description || undefined,
email: data.email || undefined,
auth: data.auth || undefined,
type: data.type,
lastLogedIn: data.lastLogedIn,
language: data.languageId,
avatarId: data.avatarId ? new Snowflake(data.avatarId) : undefined,
password: data.password
});
ctx.users.set(user.id.toString(), user);
return user;
}
/**
* Loads user from DB (OLD)
*
* @deprecated
* @param {SnowflakeResolvable} ids
* @param {any} user
* @param {RequestContext} ctx
*
* @return {User}
*/
private static userFromDB(ids: SnowflakeResolvable, user: any, ctx: RequestContext) {
return new User({
id: new Snowflake(ids),
ctx,
name: user.name,
description: user.description,
email: user.email,
auth: user.auth,
type: user.type,
lastLogedIn: new Date(user.last_loged),
language: user.language,
reported: user.reported
});
}
/**
* Loads user from DB
* @param {number|Snowflake|string} id User snowflake to load
* @param {RequestContext} ctx
* @param {UserClient} client
* @return {Promise<User>} user
*/
static async getUser(id: UserResolvable, ctx: RequestContext):
Promise<User> {
if(User.isUser(id)) return id as User;
if(ctx.users.has(id.toString())) return ctx.users.get(id.toString())!;
if(ctx.user && ctx.user.id.toString() == id.toString()) return ctx.user;
let useSnowflake = false;
if(/^[0-9]+$/.test(id.toString())) useSnowflake = true;
const userData = await prisma.user.findFirstOrThrow({
where: {
OR: [...(useSnowflake ? [{
snowflake: new Snowflake(id).toBigInt()
}] : []), {
name: id.toString()
}]
},
orderBy: {
snowflake: "desc"
}
});
const user = this.fromDatabase(userData, ctx);
if(ctx.client) {
const reported = await prisma.modQueue.findFirst({
where: {
reporterId: ctx.client.user.id.toBigInt(),
userId: user.id.toBigInt()
}
});
user.reported = !!reported;
}
return user;
}
/**
* Searches users
*
* @param {UserFilters} filters
* @return {Promise<Collection<string, User>>}
*/
static searchUsers(filters: UserFilters): Promise<Collection<string, User>> {
const { query, variables } = filters.buildQuery();
return new Promise((resolve, reject) => {
filters.ctx.conn.query(query, variables, async (err, res) => {
if(err) return void reject(err);
const users = new Collection<string, User>();
for(const user of res) {
const u = User.userFromDB(
new Snowflake(BigInt(user.snowflake)), user, filters.ctx
);
if(user.avatar) u.avatar = await File.load(user.avatar, filters.ctx);
users.set(u.id.toString(), u);
}
resolve(users);
});
});
}
/**
* Returns number of results for a specific search
*
* @param {UserFilters} filters
* @return {number}
*/
static searchUsersCount(filters: UserFilters): Promise<number> {
const { query, variables } = filters.buildCountQuery();
return new Promise((resolve, reject) => {
filters.ctx.conn.query(query, variables, (err, res) => {
if(err) return void reject(err);
resolve(res[0].count);
});
});
}
/**
* Gets user's avatar
*
* @return {Promise<File|undefined>}
*/
async getAvatar(): Promise<File | undefined> {
if(!this.avatarId) return undefined;
if(this.avatar) return this.avatar;
return await File.load(this.avatarId, this.ctx);
}
/**
* Counts friends
* @return {Promise<number>}
*/
async countFollowers(): Promise<number> {
const res = await prisma.userRelation.count({
where: {
targetId: this.id.toBigInt(),
type: 1
}
});
return res;
}
/**
* Gets relation between client and specified user
* @param {UserResolvable} uid
* @return {Promise<UserRelation>}
*/
async getRelation(uid: UserResolvable):
Promise<UserRelation|null> {
const res = await prisma.userRelation.findFirst({
where: {
authorId: this.id.toBigInt(),
targetId: BigInt(User.resolveUserID(uid))
}
});
if(!res) return null;
return UserRelation.fromDatabase(res, this.ctx);
}
/**
* Gets followed users
*
* @return {Promise<UserRelation[]>}
*/
async getFollows(): Promise<UserRelation[]> {
const relations = await prisma.userRelation.findMany({
where: {
authorId: this.id.toBigInt(),
type: 1
},
include: {
target: true
}
});
return await Promise.all(
relations.map((data) =>UserRelation.fromDatabase(data, this.ctx))
);
}
/**
* Gets followers
*
* @return {Promise<UserRelation[]>}
*/
async getFollowers(): Promise<UserRelation[]> {
const relations = await prisma.userRelation.findMany({
where: {
targetId: this.id.toBigInt(),
type: 1
},
include: {
author: true
}
});
return await Promise.all(
relations.map((data) => UserRelation.fromDatabase(data, this.ctx))
);
}
/**
* Gets animations created by user
* @param {number} max Max animations per page
* @param {number} page Current page to show
* @return {Promise<Collection<SnowflakeString, BasicAnimation>>}
*/
async getAnimations(max: number, page: number):
Promise<Collection<SnowflakeString, BasicAnimation>> {
const animations = await prisma.animation.findMany({
where: {
authorId: this.id.toBigInt(),
category: this.ctx.client && this.ctx.client.user.type >= UserType.MODERATOR ? {
snowflake: {
not: 0
}
} : undefined,
published: this.ctx.client &&
(this.ctx.client.user.id.toString() === this.id.toString() ||
this.ctx.client.user.type >= UserType.MODERATOR) ?
undefined : {
not: null
}
},
orderBy: {
snowflake: "desc"
},
skip: page * max,
take: max
});
var col = new Collection<SnowflakeString, BasicAnimation>();
for(var anim of animations) {
col.set(anim.snowflake.toString(), await BasicAnimation.fromDatabase(anim, this.ctx));
}
return col;
}
/**
* Gets number of animations created by user
* @return {Promise<number>}
*/
async getAnimationsCount(): Promise<number> {
return prisma.animation.count({
where: {
authorId: this.id.toBigInt()
}
});
}
/**
* Gets if the user has active premium or not.
* Should not be used for 'is user getting billed' - can be mod for that.
*
* @return {boolean}
*/
hasPremium(): boolean {
return this.type >= UserType.PREMIUM;
// if higher than premium, user is mod which also includes having premium perks.
}
/**
* Gets ratings created by user
* @param {number} max Max items per page
* @param {number} page Current page
* @return {Promise<Collection<SnowflakeString, Rating>>}
*/
async getRatings(max: number, page: number):
Promise<Collection<SnowflakeString, Rating>> {
const ratings = await prisma.rating.findMany({
where: {
authorId: this.id.toBigInt()
},
orderBy: {
snowflake: "desc"
},
include: {
animation: {
include: {
author: true
}
}
},
skip: page * max,
take: max
});
var col = new Collection<SnowflakeString, Rating>();
for(var rating of ratings) {
col.set(rating.snowflake.toString(), await Rating.fromDatabase(rating, this.ctx));
}
return col;
}
/**
* Gets number of ratings created by user
* @return {Promise<number>}
*/
async getRatingsCount(): Promise<number> {
return prisma.rating.count({
where: {
authorId: this.id.toBigInt()
}
});
}
/**
* Gets files uploaded by user
* @param {number} limit Max items per page
* @param {number} page Current page
* @param {boolean} [publicOnly=true] If true, only public files are returned
* @return {Promise<Collection<SnowflakeString, File>>}
*/
async getFiles(limit: number, page: number, publicOnly: boolean = false):
Promise<Collection<SnowflakeString, File>> {
const files = await prisma.file.findMany({
where: {
authorId: this.id.toBigInt(),
state: publicOnly ? FileState.PUBLIC : undefined
},
orderBy: {
snowflake: "desc"
},
skip: page * limit,
take: limit
});
var col = new Collection<SnowflakeString, File>();
for(var file of files) {
col.set(file.snowflake.toString(), await File.fromDatabase(file, this.ctx));
}
return col;
}
/**
* Gets number of files uploaded by user
* @param {boolean} [publicOnly=true] If true, only public files are counted
* @return {Promise<number>}
*/
getFilesCount(publicOnly: boolean = false): Promise<number> {
return prisma.file.count({
where: {
authorId: this.id.toBigInt(),
state: publicOnly ? FileState.PUBLIC : undefined
}
});
}
/**
* Gets comments created by user
* @param {number} limit Max items per page
* @param {number} page Current page
* @return {Promise<Collection<SnowflakeString, Comment>>}
*/
async getComments(limit: number, page: number):
Promise<Collection<SnowflakeString, Comment>> {
const comments = await prisma.comment.findMany({
where: {
authorId: this.id.toBigInt()
},
orderBy: {
snowflake: "desc"
},
include: {
animation: {
include: {
author: true
}
}
},
skip: page * limit,
take: limit
});
var col = new Collection<SnowflakeString, Comment>();
for(var comment of comments) {
col.set(comment.snowflake.toString(), await Comment.fromDatabase(comment, this.ctx));
}
return col;
}
/**
* Compares positions of two users to determine rights
* @param {User} user to compare
*/
comparePosition(user: User):boolean;
/**
* Compares position of user and type
* @param {UserType} type to compare
*/
comparePosition(type: UserType): boolean;
/**
* Compares position of user and user/type
* @param {User | UserType} userOrType to compare
* @return {boolean}
*/
comparePosition(userOrType: User | UserType): boolean {
if(this.type < 2) return false;
if(userOrType instanceof User) {
return userOrType.type < this.type;
} else {
return userOrType < this.type;
}
}
/**
* Fetches system relation
* @return {Promise<SystemRelation>}
*/
async getType(): Promise<SystemRelation> {
if(!this.ctx.client) throw new Error("client_required");
const relationData = await prisma.systemRelation.findFirst({
where: {
targetId: this.id.toBigInt()
},
orderBy: {
snowflake: "desc"
}
});
if(!relationData) throw new Error("member");
return SystemRelation.fromDatabase(relationData, this.ctx);
}
/**
* Sets new type of user
* @param {RequestContext} ctx
* @param {UserType} type New type to set
* @return {Promise<void>}
*/
async setType(ctx: RequestContext, type: UserType): Promise<void> {
if(!ctx.user || !ctx.user.comparePosition(this)) throw new Error("perms");
await SystemRelation.createRelation(ctx, this, type);
await prisma.user.update({
where: {
snowflake: this.id.toBigInt()
},
data: {
type
}
});
}
/**
* Returns JSON-compatible object to be used in stringify
* @return {Object}
*/
toJSON() {
return {
id: this.id.getSnowflake(),
avatar: this.avatarId?.toString(),
avatarURL: this.avatarId ? File.formatPublicURL(this.avatarId.toString()) : null,
registered: this.id.time,
name: this.name,
description: this.description,
language: LanguageCodes[this.language],
type: this.type,
lastLogedIn: this.lastLogedIn,
reported: this.reported
};
}
}
export default User;
Source