Skip to content

Commit 4e224fb

Browse files
authored
Merge pull request #8 from mahesh548/dev
This merge introduces critical backend functionalities that support download access control and admin role management. Key updates include: Creation of updateDownload route for handling download permission requests. Addition of admin route with endpoints for admin actions and user statistics. Implementation of searchUser and updateRole to enable admin-level user management. Integration of transactional email notifications to inform users and admins of role and permission updates. All changes have been tested locally and verified for stability and correctness. Approving this merge for deployment to ensure complete feature parity with the frontend and enable full admin functionality across the HTh Beats platform.
2 parents 02c23c5 + a22b009 commit 4e224fb

8 files changed

Lines changed: 376 additions & 15 deletions

File tree

Database

Routes/Controller/admin.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
const Users = require("../../Database/Models/Users");
2+
const Entity = require("../../Database/Models/Entity");
3+
const Song = require("../../Database/Models/Song");
4+
const utils = require("../../utils");
5+
const admin = async (req, res) => {
6+
const { user, adminAction } = req.body;
7+
if (!user || user?.role !== "admin") {
8+
return res.status(403).json({ status: false, msg: "Unauthorized access!" });
9+
}
10+
try {
11+
if (!adminAction) {
12+
return res.status(400).json({ status: false, msg: "Invalid input!" });
13+
}
14+
15+
if (adminAction?.action === "getCounts") {
16+
const totalUsers = await Users.aggregate([
17+
{
18+
$facet: {
19+
approved: [
20+
{ $match: { downloadAccess: "approved" } },
21+
{ $count: "count" },
22+
],
23+
users: [{ $match: { role: "user" } }, { $count: "count" }],
24+
},
25+
},
26+
]);
27+
28+
const detailData = await Users.aggregate([
29+
{
30+
$facet: {
31+
admins: [
32+
{ $match: { role: "admin" } },
33+
{ $project: { username: 1, pic: 1, createdAt: 1 } },
34+
],
35+
requests: [
36+
{ $match: { downloadAccess: "requested" } },
37+
{ $project: { username: 1, pic: 1, createdAt: 1, id: 1 } },
38+
],
39+
},
40+
},
41+
]);
42+
43+
const UsersCount = Object.fromEntries(
44+
Object.entries(totalUsers[0]).map(([key, value]) => [
45+
key,
46+
value[0]?.count || 0,
47+
])
48+
);
49+
50+
UsersCount.admin = detailData[0].admins?.length || 0;
51+
UsersCount.request = detailData[0].requests?.length || 0;
52+
53+
const totalEntity = await Entity.aggregate([
54+
{
55+
$facet: {
56+
playlists: [{ $match: { type: "playlist" } }, { $count: "count" }],
57+
albums: [{ $match: { type: "album" } }, { $count: "count" }],
58+
mixes: [{ $match: { type: "mix" } }, { $count: "count" }],
59+
},
60+
},
61+
]);
62+
const EntityCount = Object.fromEntries(
63+
Object.entries(totalEntity[0]).map(([key, value]) => [
64+
key,
65+
value[0]?.count || 0,
66+
])
67+
);
68+
69+
const songCount = await Song.countDocuments({});
70+
EntityCount.songs = songCount;
71+
72+
return res.status(200).json({
73+
status: true,
74+
data: { UsersCount, EntityCount, detailData: detailData[0] },
75+
});
76+
}
77+
if (adminAction?.action === "searchUser") {
78+
const { searchId } = adminAction;
79+
if (!searchId) {
80+
return res.status(400).json({ status: false, msg: "Invalid input!" });
81+
}
82+
const isValidUsername = utils.isValidUsername(searchId);
83+
if (!isValidUsername && !utils.isValidEmail(searchId)) {
84+
return res.status(400).json({ status: false, msg: "Invalid input!" });
85+
}
86+
87+
const usersData = await Users.find(
88+
{
89+
username: { $regex: new RegExp("^" + searchId), $options: "i" },
90+
},
91+
["username", "id", "pic", "role", "createdAt", "downloadAccess", "-_id"]
92+
);
93+
94+
return res.status(200).json({
95+
status: true,
96+
data: usersData,
97+
});
98+
}
99+
100+
if (adminAction?.action === "updateRole") {
101+
const { targetId, role } = adminAction;
102+
if (!targetId || !role) {
103+
return res.status(400).json({ status: false, msg: "Invalid input!" });
104+
}
105+
const validRoles = ["user", "admin"];
106+
if (!validRoles.includes(role)) {
107+
return res.status(400).json({ status: false, msg: "Invalid role!" });
108+
}
109+
110+
const userData = await Users.findOneAndUpdate(
111+
{ id: targetId },
112+
{ $set: { role: role } },
113+
{ new: true }
114+
);
115+
116+
if (!userData) {
117+
return res.status(404).json({ status: false, msg: "User not found!" });
118+
}
119+
sendEmail(
120+
userData?.username,
121+
[{ email: userData?.email, name: userData?.username }],
122+
role
123+
);
124+
125+
return res.status(200).json({
126+
status: true,
127+
msg: `User role updated successfully!`,
128+
});
129+
}
130+
return res
131+
.status(200)
132+
.json({ status: false, msg: "Invalid admin action!" });
133+
} catch (error) {
134+
console.log(error);
135+
return res
136+
.status(500)
137+
.json({ status: false, msg: "something went wrong!" });
138+
}
139+
};
140+
141+
const sendEmail = async (username, emails, type) => {
142+
if (!username || !emails || !type) return;
143+
let message = "";
144+
let subject = "";
145+
146+
if (type == "admin") {
147+
subject = "Welcome to the HTh Beats Admin Team!";
148+
149+
message =
150+
`Dear ${username},\n\n` +
151+
`Congratulations! 🎉 You have been promoted to the Admin Team on HTh Beats.\n\n` +
152+
`As an admin, you now have access to several new responsibilities and capabilities:\n` +
153+
`• Approve or deny download requests submitted by users\n` +
154+
`• Grant or revoke download permissions, even if a request hasn't been made\n` +
155+
`• Promote other users to admin or remove current admins\n` +
156+
`• View internal statistics related to HTh Beats' usage and activity\n\n` +
157+
`To access the Admin Panel, please use the link below:\n` +
158+
`${process.env.FURL}/admin\n\n` +
159+
`If the above link doesn’t work for any reason, simply open your account settings within the app and look for the “Admin Panel” button.\n\n` +
160+
`We trust that you will use your new role responsibly and help maintain the quality and fairness of our platform. If for any reason you do not wish to continue as an admin, you can contact our support team at ${process.env.SUPPORT_MAIL}.\n\n` +
161+
`Enjoy your new role, and thank you for being a valuable part of the HTh Beats community.\n\n` +
162+
`Best regards,\n` +
163+
`The HTh Beats Team`;
164+
}
165+
if (type == "user") {
166+
subject = "Your Admin Role Has Been Revoked on HTh Beats";
167+
168+
message =
169+
`Dear ${username},\n\n` +
170+
`We’d like to inform you that your admin privileges on HTh Beats have been revoked, and your account has been returned to a regular user role.\n\n` +
171+
`This means you will no longer be able to:\n` +
172+
`• Approve or deny download requests\n` +
173+
`• Grant or revoke download access for users\n` +
174+
`• Add or remove admins\n` +
175+
`• View administrative statistics or privileged data\n\n` +
176+
`This decision was made by the HTh Beats Admin Team. We ask you to please respect this decision as part of maintaining the integrity and responsibility expected from admin roles.\n\n` +
177+
`However, if you believe this action was taken in error or would like clarification, feel free to contact us at ${process.env.SUPPORT_MAIL} and we’ll be happy to assist you.\n\n` +
178+
`Thank you for your past contributions as an admin. We hope you’ll continue enjoying HTh Beats as a valued user.\n\n` +
179+
`Sincerely,\n` +
180+
`The HTh Beats Team`;
181+
}
182+
if (!subject || !message) return;
183+
await utils.sendMail(emails, subject, message);
184+
const securityMessage =
185+
`Dear Mahesh,\n\n` +
186+
`There is new changes in HTh Beats Admin Team as @${username} has been ${
187+
type == "admin" ? "added to" : "removed from"
188+
} the Team. \n\n` +
189+
`New admin's email: ${emails[0]?.email} \n` +
190+
`New admin's username: ${emails[0]?.name} \n\n` +
191+
`The HTh Beats Safety System`;
192+
await utils.sendMail(
193+
[{ email: "mkmjnp5@gmail.com", name: "Mahesh" }],
194+
"Changes in HTh Beats Admin Team",
195+
securityMessage
196+
);
197+
};
198+
199+
module.exports = admin;
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
const Users = require("../../Database/Models/Users");
2+
const utils = require("../../utils");
3+
4+
const updateDownload = async (req, res) => {
5+
const { accessData, user } = req.body;
6+
if (!accessData)
7+
return res.status(200).json({ status: false, msg: "invalid input!" });
8+
9+
try {
10+
const { access } = accessData;
11+
if (!access)
12+
return res.status(400).json({ status: false, msg: "invalid input!" });
13+
14+
if (access === "requested") {
15+
const updatedUser = await Users.findOneAndUpdate(
16+
{ id: user.id },
17+
{ $set: { downloadAccess: "requested" } },
18+
{ new: true }
19+
);
20+
21+
let adminsMail = await Users.find({ role: "admin" }, [
22+
"email",
23+
"username",
24+
]);
25+
adminsMail = adminsMail.map((item) => {
26+
return { email: item.email, name: item.username };
27+
});
28+
29+
sendEmail(updatedUser?.username, adminsMail, "request");
30+
31+
return res
32+
.status(200)
33+
.json({ status: true, msg: "Download access requested successfully!" });
34+
}
35+
36+
if (access === "approved" || access === "default") {
37+
const { targetId } = accessData;
38+
if (!targetId)
39+
return res.status(400).json({ status: false, msg: "invalid input!" });
40+
const adminData = await Users.findOne({ id: user.id, role: "admin" });
41+
if (!adminData)
42+
return res
43+
.status(403)
44+
.json({ status: false, msg: "unauthorized access!" });
45+
46+
const updatedUser = await Users.findOneAndUpdate(
47+
{ id: targetId },
48+
{ $set: { downloadAccess: access } },
49+
{ new: true }
50+
);
51+
52+
sendEmail(
53+
updatedUser?.username,
54+
[{ email: updatedUser?.email, name: updatedUser?.username }],
55+
access
56+
);
57+
58+
return res
59+
.status(200)
60+
.json({ status: true, msg: "Download access updated successfully!" });
61+
}
62+
63+
return res.status(200).json({ status: false, msg: "invalid access type!" });
64+
} catch (error) {
65+
console.log(error);
66+
res.status(500).json({ status: false });
67+
}
68+
};
69+
70+
const sendEmail = async (username, emails, type) => {
71+
if (!username || !emails || !type) return;
72+
let message = "";
73+
let subject = "";
74+
if (type == "request") {
75+
const pending = await Users.countDocuments({ downloadAccess: "requested" });
76+
subject = `${pending} Pending Download Request${
77+
pending > 1 ? "s" : ""
78+
} - Action Required`;
79+
80+
message =
81+
`Dear HTh Beats Admin Team,\n\n` +
82+
`A new download request has been submitted by user @${username}.\n\n` +
83+
`There are currently (${pending}) pending download request${
84+
pending > 1 ? "s" : ""
85+
} awaiting your review.\n\n` +
86+
`Please visit the Admin Panel to take the necessary actions:\n` +
87+
`${process.env.FURL}/admin\n\n` +
88+
`IF LINK ABOVE NOT WORKING THEN SIMPLY OPEN YOUR ACCOUNT AND ACCESS ADMIN PANEL FROM SETTINGS\n\n` +
89+
`Your continued support helps keep HTh Beats running smoothly and ensures a great experience for our users.\n` +
90+
`We appreciate your attention to this matter.\n\n` +
91+
`Best regards,\n` +
92+
`The HTh Beats Team`;
93+
}
94+
if (type == "approved") {
95+
subject = "Your Download Request Has Been Approved!";
96+
97+
message =
98+
`Dear ${username},\n\n` +
99+
`Congratulations! Your download request has been approved, and the download feature is now unlocked for your account.\n\n` +
100+
`You can now enjoy downloading your favorite music directly from the app.\n\n` +
101+
`We kindly remind you that all downloaded content is for personal use only. Please refrain from using the material for any commercial purposes and help us maintain a piracy-free and respectful environment.\n\n` +
102+
`If you experience any issues accessing the download feature, please don't hesitate to contact our support team at ${process.env.SUPPORT_MAIL}.\n\n` +
103+
`Thank you for being a valued part of the HTh Beats community. We hope you enjoy the enhanced experience!\n\n` +
104+
`Best regards,\n` +
105+
`The HTh Beats Team`;
106+
}
107+
if (type == "default") {
108+
subject = "Your Download Request Was Not Approved";
109+
110+
message =
111+
`Dear ${username},\n\n` +
112+
`Thank you for your interest in accessing the download feature on HTh Beats.\n\n` +
113+
`After reviewing your request, we regret to inform you that it has not been approved at this time.\n\n` +
114+
`This feature is selectively granted based on internal criteria, and unfortunately, your account did not meet the requirements for approval.\n\n` +
115+
`We encourage you to continue enjoying all the other features HTh Beats has to offer and welcome you to reapply in the future if needed.\n\n` +
116+
`If you believe this decision was made in error or if you have any questions, feel free to reach out to our support team at ${process.env.SUPPORT_MAIL}.\n\n` +
117+
`Thank you for being a part of the HTh Beats community.\n\n` +
118+
`Best regards,\n` +
119+
`The HTh Beats Team`;
120+
}
121+
if (!subject || !message) return;
122+
utils.sendMail(emails, subject, message);
123+
};
124+
125+
module.exports = updateDownload;

Routes/Controller/userData.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const userData = async (req, res) => {
1212
"languages",
1313
"pic",
1414
"email",
15+
"role",
16+
"downloadAccess",
1517
"-_id",
1618
]).lean();
1719
const email = usersData.email;

Routes/Middlewares/authentication.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const auth = async (req, res, next) => {
2727
{ expiresIn: "30d" }
2828
);
2929

30-
req.body.user = user;
30+
req.body.user = { ...user, role: realUser?.role };
3131
//setting session token into headers
3232
res.setHeader("Access-Control-Expose-Headers", "session");
3333
res.setHeader("session", refreshedToken);

Routes/Router.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const room = require("./Controller/room.js");
4747
const uploadPic = require("./Controller/uploadPic.js");
4848
const deletePic = require("./Controller/deletePic.js");
4949
const deleteAccount = require("./Controller/deleteAccount.js");
50+
const updateDownload = require("./Controller/updateDownload.js");
51+
const admin = require("./Controller/admin.js");
5052

5153
//routes
5254
router.post("/signup", [userMailValidator], signUp);
@@ -75,6 +77,10 @@ router.delete("/activity", [auth], deleteActivity);
7577

7678
router.post("/room/:event", [auth], room);
7779

80+
router.post("/updateAccess", [auth], updateDownload);
81+
82+
router.post("/admin", [auth], admin);
83+
7884
router.post("/profile_pic", [upload.single("image"), auth], uploadPic);
7985
router.delete("/profile_pic", [auth], deletePic);
8086

0 commit comments

Comments
 (0)