- Added a new SQL field to indicate pending invitations for team members, improving role management logic. - Updated the settings drawer component to display tooltips for roles that cannot be changed, enhancing user experience. - Introduced new localization entries for pending invitations and role change restrictions in English, Spanish, and Portuguese, ensuring consistency across languages.
779 lines
36 KiB
TypeScript
779 lines
36 KiB
TypeScript
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 {calculateMonthDays, getColor, log_error, megabytesToBytes} from "../shared/utils";
|
|
import moment from "moment";
|
|
import {calculateStorage} from "../shared/s3";
|
|
import {checkTeamSubscriptionStatus, getActiveTeamMemberCount, getCurrentProjectsCount, getFreePlanSettings, getOwnerIdByTeam, getTeamMemberCount, getUsedStorage} from "../shared/paddle-utils";
|
|
import {
|
|
addModifier,
|
|
cancelSubscription,
|
|
changePlan,
|
|
generatePayLinkRequest,
|
|
pauseOrResumeSubscription,
|
|
updateUsers
|
|
} from "../shared/paddle-requests";
|
|
import {statusExclude} from "../shared/constants";
|
|
import {NotificationsService} from "../services/notifications/notifications.service";
|
|
import {SocketEvents} from "../socket.io/events";
|
|
import {IO} from "../shared/io";
|
|
|
|
export default class AdminCenterController extends WorklenzControllerBase {
|
|
|
|
public static async checkIfUserActiveInOtherTeams(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
|
|
JOIN team_members AS tm ON tmi.team_member_id = tm.id
|
|
WHERE tmi.email = $1::TEXT
|
|
AND t.user_id = $2::UUID AND tm.active = true);`;
|
|
const result = await db.query(q, [email, owner_id]);
|
|
|
|
const [data] = result.rows;
|
|
return data.exists;
|
|
}
|
|
|
|
// organization
|
|
@HandleExceptions()
|
|
public static async getOrganizationDetails(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
// const q = `SELECT organization_name AS name,
|
|
// contact_number,
|
|
// contact_number_secondary,
|
|
// (SELECT email FROM users WHERE id = users_data.user_id),
|
|
// (SELECT name FROM users WHERE id = users_data.user_id) AS owner_name
|
|
// FROM users_data
|
|
// WHERE user_id = $1;`;
|
|
const q = `SELECT organization_name AS name,
|
|
contact_number,
|
|
contact_number_secondary,
|
|
(SELECT email FROM users WHERE id = organizations.user_id),
|
|
(SELECT name FROM users WHERE id = organizations.user_id) AS owner_name
|
|
FROM organizations
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
return res.status(200).send(new ServerResponse(true, data));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getOrganizationAdmins(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT u.name, email, owner AS is_owner
|
|
FROM users u
|
|
LEFT JOIN team_members tm ON u.id = tm.user_id
|
|
LEFT JOIN roles r ON tm.role_id = r.id
|
|
WHERE tm.team_id IN (SELECT id FROM teams WHERE teams.user_id = $1)
|
|
AND (admin_role IS TRUE OR owner IS TRUE)
|
|
GROUP BY u.name, email, owner
|
|
ORDER BY owner DESC, u.name;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
|
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getOrganizationUsers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const { searchQuery, size, offset } = this.toPaginationOptions(req.query, ["outer_tmiv.name", "outer_tmiv.email"]);
|
|
|
|
const q = `SELECT ROW_TO_JSON(rec) AS users
|
|
FROM (SELECT COUNT(*) AS total,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t))), '[]'::JSON)
|
|
FROM (SELECT email,
|
|
STRING_AGG(DISTINCT CAST(user_id AS VARCHAR), ', ') AS user_id,
|
|
STRING_AGG(DISTINCT name, ', ') AS name,
|
|
STRING_AGG(DISTINCT avatar_url, ', ') AS avatar_url,
|
|
(SELECT twl.created_at
|
|
FROM task_work_log twl
|
|
WHERE twl.user_id IN (SELECT tmiv.user_id
|
|
FROM team_member_info_view tmiv
|
|
WHERE tmiv.email = outer_tmiv.email)
|
|
ORDER BY created_at DESC
|
|
LIMIT 1) AS last_logged
|
|
FROM team_member_info_view outer_tmiv
|
|
WHERE outer_tmiv.team_id IN (SELECT id
|
|
FROM teams
|
|
WHERE teams.user_id = $1) ${searchQuery}
|
|
GROUP BY email
|
|
ORDER BY email LIMIT $2 OFFSET $3) t) AS data
|
|
FROM (SELECT DISTINCT email
|
|
FROM team_member_info_view outer_tmiv
|
|
WHERE outer_tmiv.team_id IN
|
|
(SELECT id
|
|
FROM teams
|
|
WHERE teams.user_id = $1) ${searchQuery}) AS total) rec;`;
|
|
const result = await db.query(q, [req.user?.owner_id, size, offset]);
|
|
const [data] = result.rows;
|
|
|
|
return res.status(200).send(new ServerResponse(true, data.users));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async updateOrganizationName(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {name} = req.body;
|
|
// const q = `UPDATE users_data
|
|
// SET organization_name = $1
|
|
// WHERE user_id = $2;`;
|
|
const q = `UPDATE organizations
|
|
SET organization_name = $1
|
|
WHERE user_id = $2;`;
|
|
const result = await db.query(q, [name, req.user?.owner_id]);
|
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async updateOwnerContactNumber(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {contact_number} = req.body;
|
|
const q = `UPDATE organizations
|
|
SET contact_number = $1
|
|
WHERE user_id = $2;`;
|
|
const result = await db.query(q, [contact_number, req.user?.owner_id]);
|
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = ``;
|
|
const result = await db.query(q, []);
|
|
const [data] = result.rows;
|
|
return res.status(200).send(new ServerResponse(true, data));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getOrganizationTeams(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {searchQuery, size, offset} = this.toPaginationOptions(req.query, ["name"]);
|
|
|
|
let size_changed = size;
|
|
|
|
if (offset == 0) size_changed = size_changed - 1;
|
|
|
|
const currentTeamClosure = offset == 0 ? `,
|
|
(SELECT COALESCE(ROW_TO_JSON(c), '{}'::JSON)
|
|
FROM (SELECT id,
|
|
name,
|
|
created_at,
|
|
(SELECT count(*) FROM team_members WHERE team_id = teams.id) as members_count,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
|
FROM (SELECT CASE
|
|
WHEN u.name IS NOT NULL THEN u.name
|
|
ELSE (SELECT name
|
|
FROM email_invitations
|
|
WHERE team_member_id = team_members.id) END,
|
|
avatar_url
|
|
FROM team_members
|
|
LEFT JOIN users u on team_members.user_id = u.id
|
|
WHERE team_id = teams.id) rec) AS team_members
|
|
FROM teams
|
|
WHERE user_id = $1 AND teams.id = $4) c) AS current_team_data` : ``;
|
|
|
|
const q = `SELECT ROW_TO_JSON(rec) AS teams
|
|
FROM (SELECT COUNT(*) AS total,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t))), '[]'::JSON)
|
|
FROM (SELECT id,
|
|
name,
|
|
created_at,
|
|
(SELECT count(*) FROM team_members WHERE team_id = teams.id) as members_count,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
|
FROM (SELECT CASE
|
|
WHEN u.name IS NOT NULL THEN u.name
|
|
ELSE (SELECT name
|
|
FROM email_invitations
|
|
WHERE team_member_id = team_members.id) END,
|
|
avatar_url
|
|
FROM team_members
|
|
LEFT JOIN users u on team_members.user_id = u.id
|
|
WHERE team_id = teams.id) rec) AS team_members
|
|
FROM teams
|
|
WHERE user_id = $1 AND NOT teams.id = $4 ${searchQuery}
|
|
ORDER BY name, created_at
|
|
LIMIT $2 OFFSET $3) t) AS data
|
|
${currentTeamClosure}
|
|
FROM teams
|
|
WHERE user_id = $1 ${searchQuery}) rec;`;
|
|
const result = await db.query(q, [req.user?.owner_id, size_changed, offset, req.user?.team_id]);
|
|
|
|
const [obj] = result.rows;
|
|
|
|
for (const team of obj.teams?.data || []) {
|
|
team.names = this.createTagList(team?.team_members);
|
|
team.names.map((a: any) => a.color_code = getColor(a.name));
|
|
}
|
|
|
|
if (obj.teams.current_team_data) {
|
|
obj.teams.current_team_data.names = this.createTagList(obj.teams.current_team_data?.team_members);
|
|
obj.teams.current_team_data.names.map((a: any) => a.color_code = getColor(a.name));
|
|
}
|
|
|
|
return res.status(200).send(new ServerResponse(true, obj.teams || {}));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getTeamDetails(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {id} = req.params;
|
|
|
|
const q = `SELECT id,
|
|
name,
|
|
created_at,
|
|
(SELECT count(*) FROM team_members WHERE team_id = teams.id) as members_count,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
|
FROM (SELECT tm.id,
|
|
tm.user_id,
|
|
(SELECT name
|
|
FROM team_member_info_view
|
|
WHERE team_member_info_view.team_member_id = tm.id),
|
|
(SELECT team_member_info_view.email
|
|
FROM team_member_info_view
|
|
WHERE team_member_info_view.team_member_id = tm.id),
|
|
(SELECT team_member_info_view.avatar_url
|
|
FROM team_member_info_view
|
|
WHERE team_member_info_view.team_member_id = tm.id),
|
|
role_id,
|
|
r.name AS role_name,
|
|
EXISTS(SELECT email
|
|
FROM email_invitations
|
|
WHERE team_member_id = tm.id
|
|
AND email_invitations.team_id = tm.team_id) AS pending_invitation
|
|
FROM team_members tm
|
|
LEFT JOIN users u on tm.user_id = u.id
|
|
LEFT JOIN roles r on tm.role_id = r.id
|
|
WHERE tm.team_id = teams.id
|
|
ORDER BY r.name = 'Owner' DESC, u.name) rec) AS team_members
|
|
FROM teams
|
|
WHERE id = $1;`;
|
|
const result = await db.query(q, [id]);
|
|
|
|
const [obj] = result.rows;
|
|
|
|
obj.names = this.createTagList(obj?.team_members);
|
|
obj.names.map((a: any) => a.color_code = getColor(a.name));
|
|
|
|
return res.status(200).send(new ServerResponse(true, obj || {}));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async updateTeam(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {id} = req.params;
|
|
const {name, teamMembers} = req.body;
|
|
|
|
try {
|
|
// Update team name
|
|
const updateNameQuery = `UPDATE teams SET name = $1 WHERE id = $2 RETURNING id;`;
|
|
const nameResult = await db.query(updateNameQuery, [name, id]);
|
|
|
|
if (!nameResult.rows.length) {
|
|
return res.status(404).send(new ServerResponse(false, null, "Team not found"));
|
|
}
|
|
|
|
// Update team member roles if provided
|
|
if (teamMembers?.length) {
|
|
// Use Promise.all to handle all role updates concurrently
|
|
await Promise.all(teamMembers.map(async (member: { role_name: string; user_id: string; }) => {
|
|
const roleQuery = `
|
|
UPDATE team_members
|
|
SET role_id = (SELECT id FROM roles WHERE roles.team_id = $1 AND name = $2)
|
|
WHERE user_id = $3 AND team_id = $1
|
|
RETURNING id;`;
|
|
await db.query(roleQuery, [id, member.role_name, member.user_id]);
|
|
}));
|
|
}
|
|
|
|
return res.status(200).send(new ServerResponse(true, null, "Team updated successfully"));
|
|
} catch (error) {
|
|
log_error("Error updating team:", error);
|
|
return res.status(500).send(new ServerResponse(false, null, "Failed to update team"));
|
|
}
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getBillingInfo(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT get_billing_info($1) AS billing_info;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
const validTillDate = moment(data.billing_info.trial_expire_date);
|
|
|
|
const daysDifference = validTillDate.diff(moment(), "days");
|
|
const dateString = calculateMonthDays(moment().format("YYYY-MM-DD"), data.billing_info.trial_expire_date);
|
|
|
|
data.billing_info.expire_date_string = dateString;
|
|
|
|
if (daysDifference < 0) {
|
|
data.billing_info.expire_date_string = `Your trial plan expired ${dateString} ago`;
|
|
} else if (daysDifference === 0 && daysDifference < 7) {
|
|
data.billing_info.expire_date_string = `Your trial plan expires today`;
|
|
} else {
|
|
data.billing_info.expire_date_string = `Your trial plan expires in ${dateString}.`;
|
|
}
|
|
|
|
if (data.billing_info.billing_type === "year") data.billing_info.unit_price_per_month = data.billing_info.unit_price / 12;
|
|
|
|
const teamMemberData = await getTeamMemberCount(req.user?.owner_id ?? "");
|
|
const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id ?? "");
|
|
|
|
data.billing_info.total_used = teamMemberData.user_count;
|
|
data.billing_info.total_seats = subscriptionData.quantity;
|
|
|
|
return res.status(200).send(new ServerResponse(true, data.billing_info));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getBillingTransactions(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT subscription_payment_id,
|
|
event_time::date,
|
|
(next_bill_date::DATE - INTERVAL '1 day')::DATE AS next_bill_date,
|
|
currency,
|
|
receipt_url,
|
|
payment_method,
|
|
status,
|
|
payment_status
|
|
FROM licensing_payment_details
|
|
WHERE user_id = $1
|
|
ORDER BY created_at DESC;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
|
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getBillingCharges(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT (SELECT name FROM licensing_pricing_plans lpp WHERE id = lus.plan_id),
|
|
unit_price::numeric,
|
|
currency,
|
|
status,
|
|
quantity,
|
|
unit_price::numeric * quantity AS amount,
|
|
(SELECT event_time
|
|
FROM licensing_payment_details lpd
|
|
WHERE lpd.user_id = lus.user_id
|
|
ORDER BY created_at DESC
|
|
LIMIT 1)::DATE AS start_date,
|
|
(next_bill_date::DATE - INTERVAL '1 day')::DATE AS end_date
|
|
FROM licensing_user_subscriptions lus
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
|
|
const countQ = `SELECT subscription_id
|
|
FROM licensing_user_subscription_modifiers
|
|
WHERE subscription_id = (SELECT subscription_id
|
|
FROM licensing_user_subscriptions
|
|
WHERE user_id = $1
|
|
AND status != 'deleted'
|
|
LIMIT 1)::INT;`;
|
|
const countResult = await db.query(countQ, [req.user?.owner_id]);
|
|
|
|
return res.status(200).send(new ServerResponse(true, {plan_charges: result.rows, modifiers: countResult.rows}));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getBillingModifiers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT created_at
|
|
FROM licensing_user_subscription_modifiers
|
|
WHERE subscription_id = (SELECT subscription_id
|
|
FROM licensing_user_subscriptions
|
|
WHERE user_id = $1
|
|
AND status != 'deleted'
|
|
LIMIT 1)::INT;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
|
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getBillingConfiguration(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT name,
|
|
email,
|
|
organization_name AS company_name,
|
|
contact_number AS phone,
|
|
address_line_1,
|
|
address_line_2,
|
|
city,
|
|
state,
|
|
postal_code,
|
|
country
|
|
FROM organizations
|
|
LEFT JOIN users u ON organizations.user_id = u.id
|
|
WHERE u.id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
return res.status(200).send(new ServerResponse(true, data));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async updateBillingConfiguration(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {company_name, phone, address_line_1, address_line_2, city, state, postal_code, country} = req.body;
|
|
const q = `UPDATE organizations
|
|
SET organization_name = $1,
|
|
contact_number = $2,
|
|
address_line_1 = $3,
|
|
address_line_2 = $4,
|
|
city = $5,
|
|
state = $6,
|
|
postal_code = $7,
|
|
country = $8
|
|
WHERE user_id = $9;`;
|
|
const result = await db.query(q, [company_name, phone, address_line_1, address_line_2, city, state, postal_code, country, req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
return res.status(200).send(new ServerResponse(true, data, "Configuration Updated"));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async upgradePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {plan} = req.query;
|
|
|
|
const obj = await getTeamMemberCount(req.user?.owner_id ?? "");
|
|
const axiosResponse = await generatePayLinkRequest(obj, plan as string, req.user?.owner_id, req.user?.id);
|
|
|
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getPlans(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT
|
|
ls.default_monthly_plan AS monthly_plan_id,
|
|
lp_monthly.name AS monthly_plan_name,
|
|
ls.default_annual_plan AS annual_plan_id,
|
|
lp_monthly.recurring_price AS monthly_price,
|
|
lp_annual.name AS annual_plan_name,
|
|
lp_annual.recurring_price AS annual_price,
|
|
ls.team_member_limit,
|
|
ls.projects_limit,
|
|
ls.free_tier_storage
|
|
FROM
|
|
licensing_settings ls
|
|
JOIN
|
|
licensing_pricing_plans lp_monthly ON ls.default_monthly_plan = lp_monthly.id
|
|
JOIN
|
|
licensing_pricing_plans lp_annual ON ls.default_annual_plan = lp_annual.id;`;
|
|
const result = await db.query(q, []);
|
|
const [data] = result.rows;
|
|
|
|
const obj = await getTeamMemberCount(req.user?.owner_id ?? "");
|
|
|
|
data.team_member_limit = data.team_member_limit === 0 ? "Unlimited" : data.team_member_limit;
|
|
data.projects_limit = data.projects_limit === 0 ? "Unlimited" : data.projects_limit;
|
|
data.free_tier_storage = `${data.free_tier_storage}MB`;
|
|
data.current_user_count = obj.user_count;
|
|
data.annual_price = (data.annual_price / 12).toFixed(2);
|
|
|
|
return res.status(200).send(new ServerResponse(true, data));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async purchaseStorage(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT subscription_id
|
|
FROM licensing_user_subscriptions lus
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
await addModifier(data.subscription_id);
|
|
|
|
return res.status(200).send(new ServerResponse(true, data));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async changePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {plan} = req.query;
|
|
|
|
const q = `SELECT subscription_id
|
|
FROM licensing_user_subscriptions lus
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
const axiosResponse = await changePlan(plan as string, data.subscription_id);
|
|
|
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async cancelPlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request."));
|
|
|
|
const q = `SELECT subscription_id
|
|
FROM licensing_user_subscriptions lus
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
const axiosResponse = await cancelSubscription(data.subscription_id, req.user?.owner_id);
|
|
|
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async pauseSubscription(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request."));
|
|
|
|
const q = `SELECT subscription_id
|
|
FROM licensing_user_subscriptions lus
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
const axiosResponse = await pauseOrResumeSubscription(data.subscription_id, req.user?.owner_id, true);
|
|
|
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async resumeSubscription(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request."));
|
|
|
|
const q = `SELECT subscription_id
|
|
FROM licensing_user_subscriptions lus
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
const axiosResponse = await pauseOrResumeSubscription(data.subscription_id, req.user?.owner_id, false);
|
|
|
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getBillingStorageInfo(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT trial_in_progress,
|
|
trial_expire_date,
|
|
ud.storage,
|
|
(SELECT name AS plan_name FROM licensing_pricing_plans WHERE id = lus.plan_id),
|
|
(SELECT default_trial_storage FROM licensing_settings),
|
|
(SELECT storage_addon_size FROM licensing_settings),
|
|
(SELECT storage_addon_price FROM licensing_settings)
|
|
FROM organizations ud
|
|
LEFT JOIN users u ON ud.user_id = u.id
|
|
LEFT JOIN licensing_user_subscriptions lus ON u.id = lus.user_id
|
|
WHERE ud.user_id = $1;`;
|
|
const result = await db.query(q, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
return res.status(200).send(new ServerResponse(true, data));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getAccountStorage(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const teamsQ = `SELECT id
|
|
FROM teams
|
|
WHERE user_id = $1;`;
|
|
const teamsResponse = await db.query(teamsQ, [req.user?.owner_id]);
|
|
|
|
const storageQ = `SELECT storage
|
|
FROM organizations
|
|
WHERE user_id = $1;`;
|
|
const result = await db.query(storageQ, [req.user?.owner_id]);
|
|
const [data] = result.rows;
|
|
|
|
const storage: any = {};
|
|
storage.used = 0;
|
|
storage.total = data.storage;
|
|
|
|
for (const team of teamsResponse.rows) {
|
|
storage.used += await calculateStorage(team.id);
|
|
}
|
|
|
|
storage.remaining = (storage.total * 1024 * 1024 * 1024) - storage.used;
|
|
storage.used_percent = Math.ceil((storage.used / (storage.total * 1024 * 1024 * 1024)) * 10000) / 100;
|
|
|
|
return res.status(200).send(new ServerResponse(true, storage));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getCountries(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const q = `SELECT id, name, code
|
|
FROM countries
|
|
ORDER BY name;`;
|
|
const result = await db.query(q, []);
|
|
|
|
return res.status(200).send(new ServerResponse(true, result.rows || []));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async switchToFreePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const { id: teamId } = req.params;
|
|
|
|
const limits = await getFreePlanSettings();
|
|
const ownerId = await getOwnerIdByTeam(teamId);
|
|
|
|
if (limits && ownerId) {
|
|
if (parseInt(limits.team_member_limit) !== 0) {
|
|
const teamMemberCount = await getTeamMemberCount(ownerId);
|
|
if (parseInt(teamMemberCount) > parseInt(limits.team_member_limit)) {
|
|
return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${limits.team_member_limit} members.`));
|
|
}
|
|
}
|
|
|
|
const projectsCount = await getCurrentProjectsCount(ownerId);
|
|
if (parseInt(projectsCount) > parseInt(limits.projects_limit)) {
|
|
return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${limits.projects_limit} projects.`));
|
|
}
|
|
|
|
const usedStorage = await getUsedStorage(ownerId);
|
|
if (parseInt(usedStorage) > megabytesToBytes(parseInt(limits.free_tier_storage))) {
|
|
return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot exceed ${limits.free_tier_storage}MB of storage.`));
|
|
}
|
|
|
|
const update_q = `UPDATE organizations
|
|
SET license_type_id = (SELECT id FROM sys_license_types WHERE key = 'FREE'),
|
|
trial_in_progress = FALSE,
|
|
subscription_status = 'free',
|
|
storage = (SELECT free_tier_storage FROM licensing_settings)
|
|
WHERE user_id = $1;`;
|
|
await db.query(update_q, [ownerId]);
|
|
|
|
return res.status(200).send(new ServerResponse(true, [], "Your plan has been successfully switched to the Free Plan."));
|
|
}
|
|
return res.status(200).send(new ServerResponse(false, [], "Failed to switch to the Free Plan. Please try again later."));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async redeem(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const { code } = req.body;
|
|
|
|
const q = `SELECT * FROM licensing_coupon_codes WHERE coupon_code = $1 AND is_redeemed IS FALSE AND is_refunded IS FALSE;`;
|
|
const result = await db.query(q, [code]);
|
|
const [data] = result.rows;
|
|
|
|
if (!result.rows.length)
|
|
return res.status(200).send(new ServerResponse(false, [], "Redeem Code verification Failed! Please try again."));
|
|
|
|
const checkQ = `SELECT sum(team_members_limit) AS team_member_total FROM licensing_coupon_codes WHERE redeemed_by = $1 AND is_redeemed IS TRUE;`;
|
|
const checkResult = await db.query(checkQ, [req.user?.owner_id]);
|
|
const [total] = checkResult.rows;
|
|
|
|
if (parseInt(total.team_member_total) > 50)
|
|
return res.status(200).send(new ServerResponse(false, [], "Maximum number of codes redeemed!"));
|
|
|
|
const updateQ = `UPDATE licensing_coupon_codes
|
|
SET is_redeemed = TRUE, redeemed_at = CURRENT_TIMESTAMP,
|
|
redeemed_by = $1
|
|
WHERE id = $2;`;
|
|
await db.query(updateQ, [req.user?.owner_id, data.id]);
|
|
|
|
const updateQ2 = `UPDATE organizations
|
|
SET subscription_status = 'life_time_deal',
|
|
trial_in_progress = FALSE,
|
|
storage = (SELECT sum(storage_limit) FROM licensing_coupon_codes WHERE redeemed_by = $1),
|
|
license_type_id = (SELECT id FROM sys_license_types WHERE key = 'LIFE_TIME_DEAL')
|
|
WHERE user_id = $1;`;
|
|
await db.query(updateQ2, [req.user?.owner_id]);
|
|
|
|
return res.status(200).send(new ServerResponse(true, [], "Code redeemed successfully!"));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async deleteTeam(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {id} = req.params;
|
|
|
|
if (id == req.user?.team_id) {
|
|
return res.status(200).send(new ServerResponse(true, [], "Please switch to another team before attempting deletion.")
|
|
.withTitle("Unable to remove the presently active team!"));
|
|
}
|
|
|
|
const q = `DELETE FROM teams WHERE id = $1;`;
|
|
const result = await db.query(q, [id]);
|
|
|
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async deleteById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const {id} = req.params;
|
|
const {teamId} = req.body;
|
|
|
|
if (!id || !teamId) return res.status(200).send(new ServerResponse(false, "Required fields are missing."));
|
|
|
|
// check subscription status
|
|
const subscriptionData = await checkTeamSubscriptionStatus(teamId);
|
|
if (statusExclude.includes(subscriptionData.subscription_status)) {
|
|
return res.status(200).send(new ServerResponse(false, "Please check your subscription status."));
|
|
}
|
|
|
|
const q = `SELECT remove_team_member($1, $2, $3) AS member;`;
|
|
const result = await db.query(q, [id, req.user?.id, teamId]);
|
|
const [data] = result.rows;
|
|
|
|
const message = `You have been removed from <b>${req.user?.team_name}</b> by <b>${req.user?.name}</b>`;
|
|
|
|
// if (subscriptionData.status === "trialing") break;
|
|
if (!subscriptionData.is_credit && !subscriptionData.is_custom) {
|
|
if (subscriptionData.subscription_status === "active" && subscriptionData.quantity > 0) {
|
|
|
|
const obj = await getActiveTeamMemberCount(req.user?.owner_id ?? "");
|
|
|
|
const userActiveInOtherTeams = await this.checkIfUserActiveInOtherTeams(req.user?.owner_id as string, req.query?.email as string);
|
|
|
|
if (!userActiveInOtherTeams) {
|
|
const response = await updateUsers(subscriptionData.subscription_id, obj.user_count);
|
|
if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, response.message || "Please check your subscription."));
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
NotificationsService.sendNotification({
|
|
receiver_socket_id: data.socket_id,
|
|
message,
|
|
team: data.team,
|
|
team_id: id
|
|
});
|
|
|
|
IO.emitByUserId(data.member.id, req.user?.id || null, SocketEvents.TEAM_MEMBER_REMOVED, {
|
|
teamId: id,
|
|
message
|
|
});
|
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getFreePlanLimits(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const limits = await getFreePlanSettings();
|
|
|
|
return res.status(200).send(new ServerResponse(true, limits || {}));
|
|
}
|
|
|
|
@HandleExceptions()
|
|
public static async getOrganizationProjects(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
|
const { searchQuery, size, offset } = this.toPaginationOptions(req.query, ["p.name"]);
|
|
|
|
const countQ = `SELECT COUNT(*) AS total
|
|
FROM projects p
|
|
JOIN teams t ON p.team_id = t.id
|
|
JOIN organizations o ON t.organization_id = o.id
|
|
WHERE o.user_id = $1;`;
|
|
const countResult = await db.query(countQ, [req.user?.owner_id]);
|
|
|
|
// Query to get the project data
|
|
const dataQ = `SELECT p.id,
|
|
p.name,
|
|
t.name AS team_name,
|
|
p.created_at,
|
|
pm.member_count
|
|
FROM projects p
|
|
JOIN teams t ON p.team_id = t.id
|
|
JOIN organizations o ON t.organization_id = o.id
|
|
LEFT JOIN (
|
|
SELECT project_id, COUNT(*) AS member_count
|
|
FROM project_members
|
|
GROUP BY project_id
|
|
) pm ON p.id = pm.project_id
|
|
WHERE o.user_id = $1 ${searchQuery}
|
|
ORDER BY p.name
|
|
OFFSET $2 LIMIT $3;`;
|
|
|
|
const result = await db.query(dataQ, [req.user?.owner_id, offset, size]);
|
|
|
|
const response = {
|
|
total: countResult.rows[0]?.total ?? 0,
|
|
data: result.rows ?? []
|
|
};
|
|
|
|
return res.status(200).send(new ServerResponse(true, response));
|
|
}
|
|
}
|