diff --git a/src/server/components/UserDatabase.js b/src/server/components/UserDatabase.js index c2fb400..34a55ff 100644 --- a/src/server/components/UserDatabase.js +++ b/src/server/components/UserDatabase.js @@ -1,5 +1,6 @@ const { Collection } = require("@discordjs/collection"); const { ObjectId } = require("mongodb"); +const { inspect } = require('node:util'); const { AbstractUserDatabase } = require("../interfaces/"); const { User } = require("../structures"); @@ -23,8 +24,9 @@ class UserDatabase extends AbstractUserDatabase { } async fetchUser (id, force = false) { - + 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) }); if (!data) return null; @@ -33,15 +35,15 @@ class UserDatabase extends AbstractUserDatabase { this.cache.set(id, user); return user; - + } - async findUser (name) { - - let user = this.cache.find(u => u.name.toLowerCase() === name.toLowerCase()); + async findUser (username) { + + let user = this.cache.find(u => u.username.toLowerCase() === username.toLowerCase()); 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; user = this._createUser(data); @@ -72,29 +74,61 @@ class UserDatabase extends AbstractUserDatabase { /** * 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 * @return {*} * @memberof UserDatabase */ 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); if (user) return Promise.resolve(user); const data = await this.collection.findOne({ 'externalProfiles.discord.id': profile.id }); - if (data) user = this._createUser(data); - else { - this.logger.info(`Creating new user from Discord profile: ${profile.username} (${profile.id})`); - user = this._createUser({ type: 'user', name: profile.username }); - user.addExternalProfile('discord', profile); - await user.save(); - this.cache.set(user.id, user); + if (data) return Promise.resolve(this._createUser(data)); + + // 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})`); + user = this._createUser({ type: 'user', name: profile.username }); + user.addExternalProfile('discord', profile); + await user.save(); + this.cache.set(user.id, 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 * @@ -102,14 +136,25 @@ class UserDatabase extends AbstractUserDatabase { * @memberof UserDatabase */ 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 { - const result = await this.collection.insertOne(user.json); - user.id = result.insertedId; + const result = await this.collection.insertOne(json); + user._id = result.insertedId; } 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) { if (!data) throw new Error(`Missing data to create user`); return new User(this, data);