764 lines
35 KiB
TypeScript
764 lines
35 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, 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
|
|
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;
|
|
|
|
const updateNameQuery = `UPDATE teams
|
|
SET name = $1
|
|
WHERE id = $2;`;
|
|
await db.query(updateNameQuery, [name, id]);
|
|
|
|
if (teamMembers.length) {
|
|
teamMembers.forEach(async (element: { role_name: string; user_id: string; }) => {
|
|
const q = `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;`;
|
|
await db.query(q, [id, element.role_name, element.user_id]);
|
|
});
|
|
}
|
|
|
|
return res.status(200).send(new ServerResponse(true, [], "Team updated successfully"));
|
|
}
|
|
|
|
@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));
|
|
}
|
|
}
|