import {IWorkLenzRequest} from "../interfaces/worklenz-request"; import {IWorkLenzResponse} from "../interfaces/worklenz-response"; import db from "../config/db"; import {ServerResponse} from "../models/server-response"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; import {getColor} from "../shared/utils"; import TeamMembersController from "./team-members-controller"; import {checkTeamSubscriptionStatus} from "../shared/paddle-utils"; import {updateUsers} from "../shared/paddle-requests"; import {statusExclude} from "../shared/constants"; import {NotificationsService} from "../services/notifications/notifications.service"; export default class ProjectMembersController extends WorklenzControllerBase { public static async checkIfUserAlreadyExists(owner_id: string, email: string) { if (!owner_id) throw new Error("Owner not found."); const q = `SELECT EXISTS(SELECT tmi.team_member_id FROM team_member_info_view AS tmi JOIN teams AS t ON tmi.team_id = t.id WHERE tmi.email = $1::TEXT AND t.user_id = $2::UUID);`; const result = await db.query(q, [email, owner_id]); const [data] = result.rows; return data.exists; } public static async createOrInviteMembers(body: any) { if (!body) return; const q = `SELECT create_project_member($1) AS res;`; const result = await db.query(q, [JSON.stringify(body)]); const [data] = result.rows; const response = data.res; if (response?.notification && response?.member_user_id) { NotificationsService.sendNotification({ receiver_socket_id: response.socket_id, project: response.project, message: response.notification, project_color: response.project_color, project_id: response.project_id, team: response.team, team_id: body.team_id }); } return data; } @HandleExceptions() public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { req.body.user_id = req.user?.id; req.body.team_id = req.user?.team_id; req.body.access_level = req.body.access_level ? req.body.access_level : "MEMBER"; const data = await this.createOrInviteMembers(req.body); return res.status(200).send(new ServerResponse(true, data)); } @HandleExceptions({ raisedExceptions: { "ERROR_EMAIL_INVITATION_EXISTS": "Member already have a pending invitation that has not been accepted." } }) public static async createByEmail(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { req.body.user_id = req.user?.id; req.body.team_id = req.user?.team_id; if (!req.user?.team_id) return res.status(200).send(new ServerResponse(false, "Required fields are missing.")); // check the subscription status const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id); const userExists = await this.checkIfUserAlreadyExists(req.user?.owner_id as string, req.body.email); // Return error if user already exists if (userExists) { return res.status(200).send(new ServerResponse(false, null, "User already exists in the team.")); } // Handle self-hosted subscriptions differently if (subscriptionData.subscription_type === 'SELF_HOSTED') { // Adding as a team member const teamMemberReq: { team_id?: string; emails: string[], project_id?: string; } = { team_id: req.user?.team_id, emails: [req.body.email] }; if (req.body.project_id) teamMemberReq.project_id = req.body.project_id; const [member] = await TeamMembersController.createOrInviteMembers(teamMemberReq, req.user); if (!member) return res.status(200).send(new ServerResponse(true, null, "Failed to add the member to the project. Please try again.")); // Adding to the project const projectMemberReq = { team_member_id: member.team_member_id, team_id: req.user?.team_id, project_id: req.body.project_id, user_id: req.user?.id, access_level: req.body.access_level ? req.body.access_level : "MEMBER" }; const data = await this.createOrInviteMembers(projectMemberReq); return res.status(200).send(new ServerResponse(true, data.member)); } if (statusExclude.includes(subscriptionData.subscription_status)) { return res.status(200).send(new ServerResponse(false, null, "Unable to add user! Please check your subscription status.")); } if (!userExists && subscriptionData.is_ltd && subscriptionData.current_count && (parseInt(subscriptionData.current_count) + 1 > parseInt(subscriptionData.ltd_users))) { return res.status(200).send(new ServerResponse(false, null, "Maximum number of life time users reached.")); } // if (subscriptionData.status === "trialing") break; if (!userExists && !subscriptionData.is_credit && !subscriptionData.is_custom && subscriptionData.subscription_status !== "trialing") { // if (subscriptionData.subscription_status === "active") { // const response = await updateUsers(subscriptionData.subscription_id, (subscriptionData.quantity + 1)); // if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, null, response.message || "Unable to add user! Please check your subscription.")); // } const updatedCount = parseInt(subscriptionData.current_count) + 1; const requiredSeats = updatedCount - subscriptionData.quantity; if (updatedCount > subscriptionData.quantity) { const obj = { seats_enough: false, required_count: requiredSeats, current_seat_amount: subscriptionData.quantity }; return res.status(200).send(new ServerResponse(false, obj, null)); } } // Adding as a team member const teamMemberReq: { team_id?: string; emails: string[], project_id?: string; } = { team_id: req.user?.team_id, emails: [req.body.email] }; if (req.body.project_id) teamMemberReq.project_id = req.body.project_id; const [member] = await TeamMembersController.createOrInviteMembers(teamMemberReq, req.user); if (!member) return res.status(200).send(new ServerResponse(true, null, "Failed to add the member to the project. Please try again.")); // Adding to the project const projectMemberReq = { team_member_id: member.team_member_id, team_id: req.user?.team_id, project_id: req.body.project_id, user_id: req.user?.id, access_level: req.body.access_level ? req.body.access_level : "MEMBER" }; const data = await this.createOrInviteMembers(projectMemberReq); return res.status(200).send(new ServerResponse(true, data.member)); } @HandleExceptions() public static async get(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const q = ` SELECT project_members.id, tm.id AS team_member_id, (SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id), (SELECT name FROM team_member_info_view WHERE team_member_id = project_members.team_member_id) AS name, u.avatar_url, jt.name AS job_title FROM project_members INNER JOIN team_members tm ON project_members.team_member_id = tm.id LEFT JOIN job_titles jt ON tm.job_title_id = jt.id LEFT JOIN users u ON tm.user_id = u.id WHERE project_id = $1 ORDER BY project_members.created_at DESC; `; const result = await db.query(q, [req.params.id]); result.rows.forEach((a: any) => a.color_code = getColor(a.name)); return res.status(200).send(new ServerResponse(true, result.rows)); } public static async checkIfMemberExists(projectId: string, teamMemberId: string) { const q = `SELECT EXISTS(SELECT id FROM project_members WHERE project_id = $1::UUID AND team_member_id = $2::UUID)`; const result = await db.query(q, [projectId, teamMemberId]); const [data] = result.rows; return data.exists; } @HandleExceptions() public static async deleteById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const q = `SELECT remove_project_member($1, $2, $3) AS res;`; const result = await db.query(q, [req.params.id, req.user?.id, req.user?.team_id]); const [data] = result.rows; const response = data.res; if (response?.notification && response?.member_user_id) { NotificationsService.sendNotification({ receiver_socket_id: response.socket_id, project: response.project, message: response.notification, project_color: response.project_color, project_id: response.project_id, team: response.team, team_id: req.user?.team_id as string }); } return res.status(200).send(new ServerResponse(true, result.rows)); } }