| | const prisma = require("../utils/prisma"); |
| | const { EventLogs } = require("./eventLogs"); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | const User = { |
| | usernameRegex: new RegExp(/^[a-z0-9_\-.]+$/), |
| | writable: [ |
| | |
| | "username", |
| | "password", |
| | "pfpFilename", |
| | "role", |
| | "suspended", |
| | "dailyMessageLimit", |
| | "bio", |
| | ], |
| | validations: { |
| | username: (newValue = "") => { |
| | try { |
| | if (String(newValue).length > 100) |
| | throw new Error("Username cannot be longer than 100 characters"); |
| | if (String(newValue).length < 2) |
| | throw new Error("Username must be at least 2 characters"); |
| | return String(newValue); |
| | } catch (e) { |
| | throw new Error(e.message); |
| | } |
| | }, |
| | role: (role = "default") => { |
| | const VALID_ROLES = ["default", "admin", "manager"]; |
| | if (!VALID_ROLES.includes(role)) { |
| | throw new Error( |
| | `Invalid role. Allowed roles are: ${VALID_ROLES.join(", ")}` |
| | ); |
| | } |
| | return String(role); |
| | }, |
| | dailyMessageLimit: (dailyMessageLimit = null) => { |
| | if (dailyMessageLimit === null) return null; |
| | const limit = Number(dailyMessageLimit); |
| | if (isNaN(limit) || limit < 1) { |
| | throw new Error( |
| | "Daily message limit must be null or a number greater than or equal to 1" |
| | ); |
| | } |
| | return limit; |
| | }, |
| | bio: (bio = "") => { |
| | if (!bio || typeof bio !== "string") return ""; |
| | if (bio.length > 1000) |
| | throw new Error("Bio cannot be longer than 1,000 characters"); |
| | return String(bio); |
| | }, |
| | }, |
| | |
| | castColumnValue: function (key, value) { |
| | switch (key) { |
| | case "suspended": |
| | return Number(Boolean(value)); |
| | case "dailyMessageLimit": |
| | return value === null ? null : Number(value); |
| | default: |
| | return String(value); |
| | } |
| | }, |
| |
|
| | filterFields: function (user = {}) { |
| | const { password, ...rest } = user; |
| | return { ...rest }; |
| | }, |
| |
|
| | create: async function ({ |
| | username, |
| | password, |
| | role = "default", |
| | dailyMessageLimit = null, |
| | bio = "", |
| | }) { |
| | const passwordCheck = this.checkPasswordComplexity(password); |
| | if (!passwordCheck.checkedOK) { |
| | return { user: null, error: passwordCheck.error }; |
| | } |
| |
|
| | try { |
| | |
| | if (!this.usernameRegex.test(username)) |
| | throw new Error( |
| | "Username must only contain lowercase letters, periods, numbers, underscores, and hyphens with no spaces" |
| | ); |
| |
|
| | const bcrypt = require("bcrypt"); |
| | const hashedPassword = bcrypt.hashSync(password, 10); |
| | const user = await prisma.users.create({ |
| | data: { |
| | username: this.validations.username(username), |
| | password: hashedPassword, |
| | role: this.validations.role(role), |
| | bio: this.validations.bio(bio), |
| | dailyMessageLimit: |
| | this.validations.dailyMessageLimit(dailyMessageLimit), |
| | }, |
| | }); |
| | return { user: this.filterFields(user), error: null }; |
| | } catch (error) { |
| | console.error("FAILED TO CREATE USER.", error.message); |
| | return { user: null, error: error.message }; |
| | } |
| | }, |
| | |
| | |
| | loggedChanges: function (updates, prev = {}) { |
| | const changes = {}; |
| | const sensitiveFields = ["password"]; |
| |
|
| | Object.keys(updates).forEach((key) => { |
| | if (!sensitiveFields.includes(key) && updates[key] !== prev[key]) { |
| | changes[key] = `${prev[key]} => ${updates[key]}`; |
| | } |
| | }); |
| |
|
| | return changes; |
| | }, |
| |
|
| | update: async function (userId, updates = {}) { |
| | try { |
| | if (!userId) throw new Error("No user id provided for update"); |
| | const currentUser = await prisma.users.findUnique({ |
| | where: { id: parseInt(userId) }, |
| | }); |
| | if (!currentUser) return { success: false, error: "User not found" }; |
| | |
| | |
| | Object.entries(updates).forEach(([key, value]) => { |
| | if (this.writable.includes(key)) { |
| | if (this.validations.hasOwnProperty(key)) { |
| | updates[key] = this.validations[key]( |
| | this.castColumnValue(key, value) |
| | ); |
| | } else { |
| | updates[key] = this.castColumnValue(key, value); |
| | } |
| | return; |
| | } |
| | delete updates[key]; |
| | }); |
| |
|
| | if (Object.keys(updates).length === 0) |
| | return { success: false, error: "No valid updates applied." }; |
| |
|
| | |
| | if (updates.hasOwnProperty("password")) { |
| | const passwordCheck = this.checkPasswordComplexity(updates.password); |
| | if (!passwordCheck.checkedOK) { |
| | return { success: false, error: passwordCheck.error }; |
| | } |
| | const bcrypt = require("bcrypt"); |
| | updates.password = bcrypt.hashSync(updates.password, 10); |
| | } |
| |
|
| | if ( |
| | updates.hasOwnProperty("username") && |
| | currentUser.username !== updates.username && |
| | !this.usernameRegex.test(updates.username) |
| | ) |
| | return { |
| | success: false, |
| | error: |
| | "Username must only contain lowercase letters, periods, numbers, underscores, and hyphens with no spaces", |
| | }; |
| |
|
| | const user = await prisma.users.update({ |
| | where: { id: parseInt(userId) }, |
| | data: updates, |
| | }); |
| |
|
| | await EventLogs.logEvent( |
| | "user_updated", |
| | { |
| | username: user.username, |
| | changes: this.loggedChanges(updates, currentUser), |
| | }, |
| | userId |
| | ); |
| | return { success: true, error: null }; |
| | } catch (error) { |
| | console.error(error.message); |
| | return { success: false, error: error.message }; |
| | } |
| | }, |
| |
|
| | |
| | |
| | |
| | _update: async function (id = null, data = {}) { |
| | if (!id) throw new Error("No user id provided for update"); |
| |
|
| | try { |
| | const user = await prisma.users.update({ |
| | where: { id }, |
| | data, |
| | }); |
| | return { user, message: null }; |
| | } catch (error) { |
| | console.error(error.message); |
| | return { user: null, message: error.message }; |
| | } |
| | }, |
| |
|
| | |
| | |
| | |
| | |
| | |
| | get: async function (clause = {}) { |
| | try { |
| | const user = await prisma.users.findFirst({ where: clause }); |
| | return user ? this.filterFields({ ...user }) : null; |
| | } catch (error) { |
| | console.error(error.message); |
| | return null; |
| | } |
| | }, |
| | |
| | _get: async function (clause = {}) { |
| | try { |
| | const user = await prisma.users.findFirst({ where: clause }); |
| | return user ? { ...user } : null; |
| | } catch (error) { |
| | console.error(error.message); |
| | return null; |
| | } |
| | }, |
| |
|
| | count: async function (clause = {}) { |
| | try { |
| | const count = await prisma.users.count({ where: clause }); |
| | return count; |
| | } catch (error) { |
| | console.error(error.message); |
| | return 0; |
| | } |
| | }, |
| |
|
| | delete: async function (clause = {}) { |
| | try { |
| | await prisma.users.deleteMany({ where: clause }); |
| | return true; |
| | } catch (error) { |
| | console.error(error.message); |
| | return false; |
| | } |
| | }, |
| |
|
| | where: async function (clause = {}, limit = null) { |
| | try { |
| | const users = await prisma.users.findMany({ |
| | where: clause, |
| | ...(limit !== null ? { take: limit } : {}), |
| | }); |
| | return users.map((usr) => this.filterFields(usr)); |
| | } catch (error) { |
| | console.error(error.message); |
| | return []; |
| | } |
| | }, |
| |
|
| | checkPasswordComplexity: function (passwordInput = "") { |
| | const passwordComplexity = require("joi-password-complexity"); |
| | |
| | |
| | const complexityOptions = { |
| | min: process.env.PASSWORDMINCHAR || 8, |
| | max: process.env.PASSWORDMAXCHAR || 250, |
| | lowerCase: process.env.PASSWORDLOWERCASE || 0, |
| | upperCase: process.env.PASSWORDUPPERCASE || 0, |
| | numeric: process.env.PASSWORDNUMERIC || 0, |
| | symbol: process.env.PASSWORDSYMBOL || 0, |
| | |
| | requirementCount: process.env.PASSWORDREQUIREMENTS || 0, |
| | }; |
| |
|
| | const complexityCheck = passwordComplexity( |
| | complexityOptions, |
| | "password" |
| | ).validate(passwordInput); |
| | if (complexityCheck.hasOwnProperty("error")) { |
| | let myError = ""; |
| | let prepend = ""; |
| | for (let i = 0; i < complexityCheck.error.details.length; i++) { |
| | myError += prepend + complexityCheck.error.details[i].message; |
| | prepend = ", "; |
| | } |
| | return { checkedOK: false, error: myError }; |
| | } |
| |
|
| | return { checkedOK: true, error: "No error." }; |
| | }, |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | canSendChat: async function (user) { |
| | const { ROLES } = require("../utils/middleware/multiUserProtected"); |
| | if (!user || user.dailyMessageLimit === null || user.role === ROLES.admin) |
| | return true; |
| |
|
| | const { WorkspaceChats } = require("./workspaceChats"); |
| | const currentChatCount = await WorkspaceChats.count({ |
| | user_id: user.id, |
| | createdAt: { |
| | gte: new Date(new Date() - 24 * 60 * 60 * 1000), |
| | }, |
| | }); |
| |
|
| | return currentChatCount < user.dailyMessageLimit; |
| | }, |
| | }; |
| |
|
| | module.exports = { User }; |
| |
|