user database improvements

This commit is contained in:
Erik 2022-11-10 21:03:44 +02:00
parent 5b95593d9c
commit a9be4068b1
Signed by: Navy.gif
GPG Key ID: 811EC0CD80E7E5FB

View File

@ -1,5 +1,6 @@
const { Collection } = require("@discordjs/collection"); const { Collection } = require("@discordjs/collection");
const { ObjectId } = require("mongodb"); const { ObjectId } = require("mongodb");
const { inspect } = require('node:util');
const { AbstractUserDatabase } = require("../interfaces/"); const { AbstractUserDatabase } = require("../interfaces/");
const { User } = require("../structures"); const { User } = require("../structures");
@ -25,6 +26,7 @@ class UserDatabase extends AbstractUserDatabase {
async fetchUser (id, force = false) { async fetchUser (id, force = false) {
if (!force && this.cache.has(id)) return this.cache.get(id); if (!force && this.cache.has(id)) return this.cache.get(id);
if (id.includes('temp')) return null;
const data = await this.collection.findOne({ _id: ObjectId(id) }); const data = await this.collection.findOne({ _id: ObjectId(id) });
if (!data) return null; if (!data) return null;
@ -36,12 +38,12 @@ class UserDatabase extends AbstractUserDatabase {
} }
async findUser (name) { async findUser (username) {
let user = this.cache.find(u => u.name.toLowerCase() === name.toLowerCase()); let user = this.cache.find(u => u.username.toLowerCase() === username.toLowerCase());
if (user) return Promise.resolve(user); if (user) return Promise.resolve(user);
const data = await this.collection.findOne({ name }, { collation: { locale: 'en', strength: 2 } }); const data = await this.collection.findOne({ username }, { collation: { locale: 'en', strength: 2 } });
if (!data) return null; if (!data) return null;
user = this._createUser(data); user = this._createUser(data);
@ -72,29 +74,61 @@ class UserDatabase extends AbstractUserDatabase {
/** /**
* Retrieves or creates a user based on a discord profile * Retrieves or creates a user based on a discord profile
* * TODO: Somehow generalise this for any external OAuth profiles, passport normalises most profiles to the same fields
* @param {*} profile * @param {*} profile
* @return {*} * @return {*}
* @memberof UserDatabase * @memberof UserDatabase
*/ */
async userFromDiscord (profile) { async userFromDiscord (profile) {
// TODO: Figure out what to do when a user already exists with the same username as the discord profile
// Will break when logging in with discord to an existing account that doesn't have a linked account
let user = this.cache.find((u) => u.externalProfiles.discord?.id === profile.id); let user = this.cache.find((u) => u.externalProfiles.discord?.id === profile.id);
if (user) return Promise.resolve(user); if (user) return Promise.resolve(user);
const data = await this.collection.findOne({ 'externalProfiles.discord.id': profile.id }); const data = await this.collection.findOne({ 'externalProfiles.discord.id': profile.id });
if (data) user = this._createUser(data); if (data) return Promise.resolve(this._createUser(data));
else {
// If a user with the same username already exists, force the holder of the discord account to create a new account or log in to the other account and link them
const existing = await this.collection.findOne({ username: profile.username }, { collation: { locale: 'en', strength: 2 } });
if (existing) {
const temp = this._createUser({ type: 'user', temporary: true, displayName: profile.username });
temp.addExternalProfile('discord', profile);
// TODO: need to store the user somewhere shared across the shards
this.cache.set(temp.id, temp);
return temp;
}
this.logger.info(`Creating new user from Discord profile: ${profile.username} (${profile.id})`); this.logger.info(`Creating new user from Discord profile: ${profile.username} (${profile.id})`);
user = this._createUser({ type: 'user', name: profile.username }); user = this._createUser({ type: 'user', name: profile.username });
user.addExternalProfile('discord', profile); user.addExternalProfile('discord', profile);
await user.save(); await user.save();
this.cache.set(user.id, user); this.cache.set(user.id, user);
}
return user; return user;
} }
/**
* Creates a new user from username and password
*
* @param {*} username
* @param {*} password
* @memberof UserDatabase
*/
async createUser (username, password) {
const user = this._createUser({
username,
type: 'user'
});
await user.setPassword(password);
await this.updateUser(user);
this.cache.set(user.id, user);
return user;
}
/** /**
* Updates user entry * Updates user entry
* *
@ -102,14 +136,25 @@ class UserDatabase extends AbstractUserDatabase {
* @memberof UserDatabase * @memberof UserDatabase
*/ */
async updateUser (user) { async updateUser (user) {
if (user.id) await this.collection.updateOne({ _id: ObjectId(user.id) }, user.json); const { json } = user;
this.logger.debug(`Storing user ${inspect(user.json)}`);
if (user._id) await this.collection.updateOne({ _id: ObjectId(user._id) }, json);
else { else {
const result = await this.collection.insertOne(user.json); const result = await this.collection.insertOne(json);
user.id = result.insertedId; user._id = result.insertedId;
} }
return user; return user;
} }
/**
* Helper function for creating user object
* Not strictly necessary but it's here in case the constructor changes in a way that can easily be wrapped
*
* @param {object} data Raw user data
* @return {user}
* @memberof UserDatabase
*/
_createUser (data) { _createUser (data) {
if (!data) throw new Error(`Missing data to create user`); if (!data) throw new Error(`Missing data to create user`);
return new User(this, data); return new User(this, data);