Skip to content

Commit 2c50fa0

Browse files
authoredFeb 12, 2025··
Merge pull request #65 from WaifuAPI/staging
Staging
2 parents 64961da + 591983a commit 2c50fa0

File tree

10 files changed

+487
-147
lines changed

10 files changed

+487
-147
lines changed
 

‎package-lock.json

+168-109
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "waifu.it",
3-
"version": "4.8.0",
3+
"version": "4.9.1",
44
"description": "Random API Serving Anime stuff",
55
"author": "Aeryk",
66
"private": true,
@@ -28,7 +28,7 @@
2828
"is-interactive": "^1.0.0",
2929
"moment": "^2.29.4",
3030
"mongodb": "^3.6.9",
31-
"mongoose": "^5.13.20",
31+
"mongoose": "^8.9.5",
3232
"ora": "^5.4.1",
3333
"owoify-js": "^2.0.0",
3434
"path": "^0.12.7",
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import createError from 'http-errors';
2+
import System from '../../../models/schemas/System.js';
3+
4+
// Get membership details
5+
const getMembership = async (req, res, next) => {
6+
const key = req.headers.key;
7+
8+
// Check for valid access key in headers
9+
if (!key || key !== process.env.ACCESS_KEY) {
10+
return res.status(401).json({
11+
message: 'Unauthorized',
12+
});
13+
}
14+
15+
try {
16+
// Check if any data exists
17+
let membershipData = await System.findOne({}, { membership: 1, _id: 0 });
18+
19+
// If no data exists, insert sample data (only runs once)
20+
if (!membershipData) {
21+
membershipData = await System.findOne({}, { membership: 1, _id: 0 });
22+
}
23+
24+
// Get valid keys dynamically from schema data
25+
const validFields = Object.keys(membershipData.membership);
26+
// Parse query parameters correctly
27+
const queryParams = req.query.q ? req.query.q.split(',').map(param => param.trim()) : [];
28+
// If no query params, return full membership object
29+
if (queryParams.length === 0) {
30+
return res.status(200).json(membershipData);
31+
}
32+
33+
// Validate query parameters
34+
const selectedFields = queryParams.filter(field => validFields.includes(field));
35+
36+
if (selectedFields.length === 0) {
37+
return res.status(400).json({ message: 'Invalid query parameter(s)' });
38+
}
39+
40+
// Construct projection object dynamically
41+
const projection = selectedFields.reduce((acc, field) => ({ ...acc, [`membership.${field}`]: 1 }), { _id: 0 });
42+
43+
// Fetch only the requested fields
44+
const result = await System.findOne({}, projection);
45+
46+
if (!result) {
47+
return next(createError(404, 'No membership data found'));
48+
}
49+
50+
res.status(200).json(result);
51+
} catch (error) {
52+
return next(error);
53+
}
54+
};
55+
56+
export { getMembership };

‎src/controllers/v4/internal/user.js

+130-17
Original file line numberDiff line numberDiff line change
@@ -28,36 +28,149 @@ const retrieveUserProfile = async (req, res, next) => {
2828
};
2929

3030
/**
31-
* Fetches user profile data based on the provided user ID and Reset Token.
31+
* Processes user actions such as addquota, removequota, updaterole, banuser and updatetoken
3232
*
3333
* @param {Object} req - Express request object.
3434
* @param {Object} res - Express response object.
3535
* @param {Function} next - Express next middleware function.
36-
* @returns {Object} - User profile data.
36+
* @returns {Object} - Response with action results or errors.
3737
*/
38-
const updateUserToken = async (req, res, next) => {
38+
const processUserAction = async (req, res, next) => {
3939
const key = req.headers.key;
40+
4041
// Check for valid access key in headers
4142
if (!key || key !== process.env.ACCESS_KEY) {
4243
return res.status(401).json({
4344
message: 'Unauthorized',
4445
});
4546
}
46-
const user = await Users.findById(req.params.id);
47-
if (!user) {
48-
return res.status(404).json({ message: 'User not found' }); // User not found
49-
}
5047

51-
// Update user's token in the database
52-
await Users.updateOne(
53-
{ _id: { $eq: req.params.id } },
54-
{ $set: { token: generateToken(req.params.id, process.env.HMAC_KEY) } },
55-
);
48+
const userId = req.params.id;
49+
const { action, amount, reason, executor, expiry } = req.body; // Extract fields from the request body
5650

57-
// This will return the data however it won't be the latest one after updating the token
58-
return res.status(200).json({
59-
message: 'Token reset successfully.',
60-
});
51+
try {
52+
// Fetch user by ID
53+
const user = await Users.findById(userId);
54+
if (!user) {
55+
return res.status(404).json({ message: 'User not found' }); // User not found
56+
}
57+
58+
let updatedUser;
59+
60+
// Handle different actions
61+
switch (action) {
62+
case 'addquota':
63+
if (!amount || amount <= 0) {
64+
return res.status(400).json({ message: 'Invalid quota amount' });
65+
}
66+
user.req_quota = (user.req_quota || 0) + Number(amount);
67+
68+
// Update status history
69+
user.status_history.push({
70+
_id: user.status_history.length + 1,
71+
timestamp: new Date(),
72+
reason: reason || 'Quota added',
73+
value: `+${amount} quota`,
74+
executor: executor || 'system',
75+
});
76+
77+
updatedUser = await user.save();
78+
break;
79+
80+
case 'removequota':
81+
if (!amount || amount <= 0) {
82+
return res.status(400).json({ message: 'Invalid quota amount' });
83+
}
84+
if ((user.req_quota || 0) < amount) {
85+
return res.status(400).json({ message: 'Insufficient quota' });
86+
}
87+
user.req_quota = (user.req_quota || 0) - Number(amount);
88+
89+
// Update status history
90+
user.status_history.push({
91+
_id: user.status_history.length + 1,
92+
timestamp: new Date(),
93+
reason: reason || 'Quota removed',
94+
value: `-${amount} quota`,
95+
executor: executor || 'system',
96+
});
97+
98+
updatedUser = await user.save();
99+
break;
100+
101+
case 'ban':
102+
if (!reason) {
103+
return res.status(400).json({ message: 'Ban reason is required' });
104+
}
105+
user.banned = true;
106+
107+
// Update status history
108+
user.status_history.push({
109+
_id: user.status_history.length + 1,
110+
timestamp: new Date(),
111+
expiry: expiry || null,
112+
reason,
113+
isBanned: true,
114+
executor: executor || 'system',
115+
});
116+
117+
updatedUser = await user.save();
118+
break;
119+
case 'unban':
120+
if (!reason) {
121+
return res.status(400).json({ message: 'Unban reason is required' });
122+
}
123+
user.banned = false;
124+
125+
// Update status history
126+
user.status_history.push({
127+
_id: user.status_history.length + 1,
128+
timestamp: new Date(),
129+
expiry: expiry || null,
130+
reason,
131+
isBanned: false,
132+
executor: executor || 'system',
133+
});
134+
135+
updatedUser = await user.save();
136+
break;
137+
138+
case 'updatetoken':
139+
if (!reason) {
140+
return res.status(400).json({ message: 'Token update reason is required' });
141+
}
142+
const token = generateToken(userId, process.env.HMAC_KEY);
143+
user.token = token;
144+
145+
// Update status history
146+
user.status_history.push({
147+
_id: user.status_history.length + 1,
148+
timestamp: new Date(),
149+
reason: reason || 'Token updated',
150+
value: token,
151+
executor: executor || 'system',
152+
});
153+
154+
updatedUser = await user.save();
155+
break;
156+
157+
default:
158+
return res.status(400).json({ message: `Invalid action: ${action}` });
159+
}
160+
161+
// Respond with updated user data
162+
return res.status(200).json({
163+
success: true,
164+
message: `${action} executed successfully`,
165+
user: updatedUser,
166+
});
167+
} catch (error) {
168+
// Handle server errors
169+
return res.status(500).json({
170+
message: 'An error occurred while processing the action',
171+
error: error.message,
172+
});
173+
}
61174
};
62175

63176
/**
@@ -178,4 +291,4 @@ const getUser = async (req, res, next) => {
178291
}
179292
};
180293

181-
export { retrieveUserProfile, updateUserToken, processUserSessionAndUpdate, getUser };
294+
export { retrieveUserProfile, processUserAction, processUserSessionAndUpdate, getUser };

‎src/index.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ const setupServer = async () => {
3737
* Connecting to the MongoDB database.
3838
* @type {mongoose.Connection}
3939
*/
40-
const dbConnection = await mongoose.connect(process.env.MONGODB_URI, {
41-
useUnifiedTopology: true,
42-
useNewUrlParser: true,
43-
});
40+
const dbConnection = await mongoose.connect(process.env.MONGODB_URI, {});
4441

4542
/**
4643
* Starting the Express server and logging success message.

‎src/models/schemas/System.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import mongoose from 'mongoose';
2+
const { Schema, model } = mongoose;
3+
4+
const SystemSchema = new Schema({
5+
_id: String,
6+
membership: {
7+
features: [],
8+
plans: [
9+
{
10+
_id: String,
11+
name: { type: String, required: true, unique: true },
12+
monthlyPrice: { type: Number, required: true },
13+
annualPrice: { type: Number, required: true },
14+
current: Boolean,
15+
available: Boolean,
16+
features: [
17+
{
18+
text: String,
19+
status: { type: String, enum: ['available', 'limited', 'unavailable'] },
20+
},
21+
],
22+
},
23+
],
24+
},
25+
});
26+
27+
export default model('System', SystemSchema);

‎src/models/schemas/User.js

+26
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,50 @@ const UserSchema = new mongoose.Schema({
5050
*/
5151
status_history: [
5252
{
53+
/**
54+
* Unique identifier for the status change.
55+
* @type {string}
56+
* @required
57+
*/
58+
_id: String,
5359
/**
5460
* Timestamp of the status change.
5561
* @type {Date}
5662
* @default Date.now
5763
*/
5864
timestamp: { type: Date, default: Date.now },
5965

66+
/**
67+
* Expiry date for the status change.
68+
* @type {Date}
69+
* @default Date.now
70+
*/
71+
expiry: { type: Date },
72+
6073
/**
6174
* The reason for the status change.
6275
* @type {string}
6376
*/
6477
reason: { type: String },
6578

79+
/**
80+
* The value of the status change either quota or role or subscription or new email.
81+
* @type {string}
82+
* @default 'null'
83+
*/
84+
value: { type: String },
6685
/**
6786
* Flag indicating whether the user is banned at this status change.
6887
* @type {boolean}
6988
*/
7089
isBanned: { type: Boolean },
90+
91+
/**
92+
* Information about the staff member who performed the action.
93+
* @type {Object}
94+
* @required
95+
*/
96+
executor: String,
7197
},
7298
],
7399

‎src/routes/v4/index.js

+16
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,22 @@ import statsRoutes from './internal/stats.js';
12081208
*/
12091209
router.use('/stats', statsRoutes);
12101210

1211+
import membershipRoutes from './internal/membership.js';
1212+
1213+
/**
1214+
* @api {use} Mount Membership Routes
1215+
* @apiDescription Mount the membership-related routes for handling interactions.
1216+
* @apiName UseMembershipRoutes
1217+
* @apiGroup Routes
1218+
*
1219+
* @apiSuccess {Object} routes Membership-related routes mounted on the parent router.
1220+
*
1221+
* @function createMembershipRoutes
1222+
* @description Creates and returns a set of routes for handling interactions related to Membership.
1223+
* @returns {Object} Membership-related routes.
1224+
*/
1225+
router.use('/membership', membershipRoutes);
1226+
12111227
/**
12121228
* Exporting the router for use in other parts of the application.
12131229
* @exports {Router} router - Express Router instance with mounted routes.

‎src/routes/v4/internal/membership.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Router } from 'express';
2+
import { getMembership } from '../../../controllers/v4/internal/membership.js';
3+
import createRateLimiter from '../../../middlewares/rateLimit.js';
4+
5+
const router = Router();
6+
7+
router
8+
.route('/')
9+
/**
10+
* @api {get} v4/membership Get Membership Details
11+
* @apiDescription Retrieve membership details, including plans and features.
12+
* @apiName getMembership
13+
* @apiGroup Membership
14+
* @apiPermission user
15+
*
16+
* @apiHeader {String} Authorization System access token.
17+
*
18+
* @apiParam {String} [q] Optional query parameter to filter results.
19+
* @apiParamExample {json} Request Example:
20+
* GET /membership?q=plans&features
21+
*
22+
* @apiSuccess {Object} membership Membership object.
23+
* @apiSuccessExample {json} Success Response:
24+
* {
25+
* "membership": {
26+
* "plans": [...],
27+
* "features": [...]
28+
* }
29+
* }
30+
*
31+
* @apiError (Unauthorized 401) Unauthorized Only authenticated users can access the data.
32+
* @apiError (Forbidden 403) Forbidden Only authorized users can access the data.
33+
* @apiError (Too Many Requests 429) TooManyRequests The client has exceeded the allowed number of requests within the time window.
34+
* @apiError (Bad Request 400) BadRequest Invalid query parameter(s) provided.
35+
* @apiError (Internal Server Error 500) InternalServerError An error occurred while processing the request.
36+
*/
37+
.get(createRateLimiter(), getMembership);
38+
39+
// Export the router
40+
export default router;

‎src/routes/v4/internal/user.js

+21-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Router } from 'express';
22
import {
33
retrieveUserProfile,
4-
updateUserToken,
4+
processUserAction,
55
processUserSessionAndUpdate,
66
getUser,
77
} from '../../../controllers/v4/internal/user.js';
@@ -18,7 +18,7 @@ router
1818
* @apiGroup UserManagement
1919
* @apiPermission user
2020
*
21-
* @apiHeader {String} Authorization User's access token.
21+
* @apiHeader {String} Key Internal access token
2222
*
2323
* @apiSuccess {Object} userDetails User's details.
2424
* @apiSuccess {String} userDetails.username User's username.
@@ -48,7 +48,7 @@ router
4848
* @apiGroup UserManagement
4949
* @apiPermission sudo
5050
*
51-
* @apiHeader {String} Authorization User's access token.
51+
* @apiHeader {String} Key Internal access token
5252
*
5353
* @apiParam {String} id User's unique identifier.
5454
*
@@ -70,28 +70,34 @@ router
7070
*/
7171
.get(createRateLimiter(), retrieveUserProfile)
7272
/**
73-
* @api {patch} v4/user/profile/:id Get User Profile and Update reset the existing token
74-
* @apiDescription Update the token for a specific user
75-
* @apiName updateUserToken
73+
* @api {patch} v4/user/profile/:id Perform User Action (addquota, removequota, ban, unban, updatetoken)
74+
* @apiDescription Processes various user actions including adding/removing quota, banning/unbanning users, and updating user token.
75+
* @apiName processUserAction
7676
* @apiGroup UserManagement
7777
* @apiPermission sudo
7878
*
79-
* @apiHeader {String} Authorization User's access token.
79+
* @apiHeader {String} Key Internal access token
8080
*
8181
* @apiParam {String} id User's unique identifier.
82-
*
83-
* @apiSuccess {Object} message
84-
* @apiError (Unauthorized 401) Unauthorized Only authenticated users can access the data.
85-
* @apiError (Forbidden 403) Forbidden Only authorized users can access the data.
86-
* @apiError (Too Many Requests 429) TooManyRequests The client has exceeded the allowed number of requests within the time window.
87-
* @apiError (Internal Server Error 500) InternalServerError An error occurred while processing the rate limit.
82+
* @apiParam {String} action Action to be performed (e.g., addquota, removequota, ban, unban, updatetoken).
83+
* @apiParam {String} [amount] Amount of quota to add or remove (required for addquota/removequota).
84+
* @apiParam {String} [reason] Reason for the action (required for ban, unban, and updatetoken).
85+
* @apiParam {String} [executor] Executor of the action (optional).
86+
* @apiParam {String} [expiry] Expiry of the ban (optional).
87+
*
88+
* @apiSuccess {Object} message Success message with details of the performed action.
89+
* @apiSuccess {Object} user Updated user data after the action.
90+
* @apiError (Unauthorized 401) Unauthorized Only authenticated users can perform actions.
91+
* @apiError (Forbidden 403) Forbidden Only authorized users can perform certain actions.
92+
* @apiError (Bad Request 400) BadRequest Invalid parameters for the specified action.
93+
* @apiError (Internal Server Error 500) InternalServerError An error occurred while processing the action.
8894
*
8995
* @api {function} createRateLimiter
9096
* @apiDescription Creates a rate limiter middleware to control the frequency of requests.
9197
* @apiSuccess {function} middleware Express middleware function that handles rate limiting.
92-
*
9398
*/
94-
.patch(createRateLimiter(), updateUserToken);
99+
100+
.patch(createRateLimiter(), processUserAction);
95101

96102
// Export the router
97103
export default router;

0 commit comments

Comments
 (0)
Please sign in to comment.