Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 168 additions & 109 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "waifu.it",
"version": "4.8.0",
"version": "4.9.1",
"description": "Random API Serving Anime stuff",
"author": "Aeryk",
"private": true,
Expand Down Expand Up @@ -28,7 +28,7 @@
"is-interactive": "^1.0.0",
"moment": "^2.29.4",
"mongodb": "^3.6.9",
"mongoose": "^5.13.20",
"mongoose": "^8.9.5",
"ora": "^5.4.1",
"owoify-js": "^2.0.0",
"path": "^0.12.7",
Expand Down
56 changes: 56 additions & 0 deletions src/controllers/v4/internal/membership.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import createError from 'http-errors';
import System from '../../../models/schemas/System.js';

// Get membership details
const getMembership = async (req, res, next) => {
const key = req.headers.key;

// Check for valid access key in headers
if (!key || key !== process.env.ACCESS_KEY) {
return res.status(401).json({
message: 'Unauthorized',
});
}

try {
// Check if any data exists
let membershipData = await System.findOne({}, { membership: 1, _id: 0 });

// If no data exists, insert sample data (only runs once)
if (!membershipData) {
membershipData = await System.findOne({}, { membership: 1, _id: 0 });
}

// Get valid keys dynamically from schema data
const validFields = Object.keys(membershipData.membership);
// Parse query parameters correctly
const queryParams = req.query.q ? req.query.q.split(',').map(param => param.trim()) : [];
// If no query params, return full membership object
if (queryParams.length === 0) {
return res.status(200).json(membershipData);
}

// Validate query parameters
const selectedFields = queryParams.filter(field => validFields.includes(field));

if (selectedFields.length === 0) {
return res.status(400).json({ message: 'Invalid query parameter(s)' });
}

// Construct projection object dynamically
const projection = selectedFields.reduce((acc, field) => ({ ...acc, [`membership.${field}`]: 1 }), { _id: 0 });

// Fetch only the requested fields
const result = await System.findOne({}, projection);

if (!result) {
return next(createError(404, 'No membership data found'));
}

res.status(200).json(result);
} catch (error) {
return next(error);
}
};

export { getMembership };
147 changes: 130 additions & 17 deletions src/controllers/v4/internal/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,149 @@ const retrieveUserProfile = async (req, res, next) => {
};

/**
* Fetches user profile data based on the provided user ID and Reset Token.
* Processes user actions such as addquota, removequota, updaterole, banuser and updatetoken
*
* @param {Object} req - Express request object.
* @param {Object} res - Express response object.
* @param {Function} next - Express next middleware function.
* @returns {Object} - User profile data.
* @returns {Object} - Response with action results or errors.
*/
const updateUserToken = async (req, res, next) => {
const processUserAction = async (req, res, next) => {
const key = req.headers.key;

// Check for valid access key in headers
if (!key || key !== process.env.ACCESS_KEY) {
return res.status(401).json({
message: 'Unauthorized',
});
}
const user = await Users.findById(req.params.id);
if (!user) {
return res.status(404).json({ message: 'User not found' }); // User not found
}

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

// This will return the data however it won't be the latest one after updating the token
return res.status(200).json({
message: 'Token reset successfully.',
});
try {
// Fetch user by ID
const user = await Users.findById(userId);
if (!user) {
return res.status(404).json({ message: 'User not found' }); // User not found
}

let updatedUser;

// Handle different actions
switch (action) {
case 'addquota':
if (!amount || amount <= 0) {
return res.status(400).json({ message: 'Invalid quota amount' });
}
user.req_quota = (user.req_quota || 0) + Number(amount);

// Update status history
user.status_history.push({
_id: user.status_history.length + 1,
timestamp: new Date(),
reason: reason || 'Quota added',
value: `+${amount} quota`,
executor: executor || 'system',
});

updatedUser = await user.save();
break;

case 'removequota':
if (!amount || amount <= 0) {
return res.status(400).json({ message: 'Invalid quota amount' });
}
if ((user.req_quota || 0) < amount) {
return res.status(400).json({ message: 'Insufficient quota' });
}
user.req_quota = (user.req_quota || 0) - Number(amount);

// Update status history
user.status_history.push({
_id: user.status_history.length + 1,
timestamp: new Date(),
reason: reason || 'Quota removed',
value: `-${amount} quota`,
executor: executor || 'system',
});

updatedUser = await user.save();
break;

case 'ban':
if (!reason) {
return res.status(400).json({ message: 'Ban reason is required' });
}
user.banned = true;

// Update status history
user.status_history.push({
_id: user.status_history.length + 1,
timestamp: new Date(),
expiry: expiry || null,
reason,
isBanned: true,
executor: executor || 'system',
});

updatedUser = await user.save();
break;
case 'unban':
if (!reason) {
return res.status(400).json({ message: 'Unban reason is required' });
}
user.banned = false;

// Update status history
user.status_history.push({
_id: user.status_history.length + 1,
timestamp: new Date(),
expiry: expiry || null,
reason,
isBanned: false,
executor: executor || 'system',
});

updatedUser = await user.save();
break;

case 'updatetoken':
if (!reason) {
return res.status(400).json({ message: 'Token update reason is required' });
}
const token = generateToken(userId, process.env.HMAC_KEY);
user.token = token;

// Update status history
user.status_history.push({
_id: user.status_history.length + 1,
timestamp: new Date(),
reason: reason || 'Token updated',
value: token,
executor: executor || 'system',
});

updatedUser = await user.save();
break;

default:
return res.status(400).json({ message: `Invalid action: ${action}` });
}

// Respond with updated user data
return res.status(200).json({
success: true,
message: `${action} executed successfully`,
user: updatedUser,
});
} catch (error) {
// Handle server errors
return res.status(500).json({
message: 'An error occurred while processing the action',
error: error.message,
});
}
};

/**
Expand Down Expand Up @@ -178,4 +291,4 @@ const getUser = async (req, res, next) => {
}
};

export { retrieveUserProfile, updateUserToken, processUserSessionAndUpdate, getUser };
export { retrieveUserProfile, processUserAction, processUserSessionAndUpdate, getUser };
5 changes: 1 addition & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ const setupServer = async () => {
* Connecting to the MongoDB database.
* @type {mongoose.Connection}
*/
const dbConnection = await mongoose.connect(process.env.MONGODB_URI, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
const dbConnection = await mongoose.connect(process.env.MONGODB_URI, {});

/**
* Starting the Express server and logging success message.
Expand Down
27 changes: 27 additions & 0 deletions src/models/schemas/System.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import mongoose from 'mongoose';
const { Schema, model } = mongoose;

const SystemSchema = new Schema({
_id: String,
membership: {
features: [],
plans: [
{
_id: String,
name: { type: String, required: true, unique: true },
monthlyPrice: { type: Number, required: true },
annualPrice: { type: Number, required: true },
current: Boolean,
available: Boolean,
features: [
{
text: String,
status: { type: String, enum: ['available', 'limited', 'unavailable'] },
},
],
},
],
},
});

export default model('System', SystemSchema);
26 changes: 26 additions & 0 deletions src/models/schemas/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,50 @@ const UserSchema = new mongoose.Schema({
*/
status_history: [
{
/**
* Unique identifier for the status change.
* @type {string}
* @required
*/
_id: String,
/**
* Timestamp of the status change.
* @type {Date}
* @default Date.now
*/
timestamp: { type: Date, default: Date.now },

/**
* Expiry date for the status change.
* @type {Date}
* @default Date.now
*/
expiry: { type: Date },

/**
* The reason for the status change.
* @type {string}
*/
reason: { type: String },

/**
* The value of the status change either quota or role or subscription or new email.
* @type {string}
* @default 'null'
*/
value: { type: String },
/**
* Flag indicating whether the user is banned at this status change.
* @type {boolean}
*/
isBanned: { type: Boolean },

/**
* Information about the staff member who performed the action.
* @type {Object}
* @required
*/
executor: String,
},
],

Expand Down
16 changes: 16 additions & 0 deletions src/routes/v4/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,22 @@ import statsRoutes from './internal/stats.js';
*/
router.use('/stats', statsRoutes);

import membershipRoutes from './internal/membership.js';

/**
* @api {use} Mount Membership Routes
* @apiDescription Mount the membership-related routes for handling interactions.
* @apiName UseMembershipRoutes
* @apiGroup Routes
*
* @apiSuccess {Object} routes Membership-related routes mounted on the parent router.
*
* @function createMembershipRoutes
* @description Creates and returns a set of routes for handling interactions related to Membership.
* @returns {Object} Membership-related routes.
*/
router.use('/membership', membershipRoutes);

/**
* Exporting the router for use in other parts of the application.
* @exports {Router} router - Express Router instance with mounted routes.
Expand Down
Loading
Loading