Skip to content

Commit a3b88a0

Browse files
authored
Merge pull request #61 from WaifuAPI/staging
Staging
2 parents c89a05e + 1d134ca commit a3b88a0

File tree

9 files changed

+332
-190
lines changed

9 files changed

+332
-190
lines changed

package-lock.json

Lines changed: 91 additions & 130 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "waifu.it",
3-
"version": "4.6.0",
3+
"version": "4.7.0",
44
"description": "Random API Serving Anime stuff",
55
"author": "Aeryk",
66
"private": true,
@@ -50,4 +50,4 @@
5050
"weeb",
5151
"anime-girls"
5252
]
53-
}
53+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import createError from 'http-errors';
2+
import Stats from '../../../models/schemas/Stat.js';
3+
4+
// Get Internal Status or statistics
5+
const getStats = async (req, res, next) => {
6+
const key = req.headers.key;
7+
// Check for valid access key in headers
8+
if (!key || key !== process.env.ACCESS_KEY) {
9+
return res.status(401).json({
10+
message: 'Unauthorized',
11+
});
12+
}
13+
try {
14+
const [result] = await Stats.aggregate([
15+
// Select a random document from the results
16+
{ $sample: { size: 1 } },
17+
{ $project: { __v: 0, _id: 0 } },
18+
]);
19+
20+
if (!result) {
21+
return next(createError(404, 'Could not find any Stats'));
22+
}
23+
24+
res.status(200).json(result);
25+
26+
await Stats.findOneAndUpdate({ _id: 'systemstats' }, { $inc: { stats: 1 } });
27+
} catch (error) {
28+
await Stats.findOneAndUpdate({ _id: 'systemstats' }, { $inc: { failed_requests: 1 } });
29+
return next(error);
30+
}
31+
};
32+
33+
export { getStats };

src/middlewares/authorize.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ import Stats from '../models/schemas/Stat.js';
1616
*/
1717
const authorize = requiredRole => async (req, res, next) => {
1818
try {
19+
/**
20+
* Determine the endpoint based on the request URL.
21+
*/
22+
const endpoint = getEndpointFromUrl(req.originalUrl);
23+
24+
/**
25+
* Check if the requested endpoint is disabled.
26+
*/
27+
const isEndpointEnabled = await isEndpointEnabledInStats(endpoint);
28+
29+
if (!isEndpointEnabled) {
30+
return next(
31+
createError(
32+
403,
33+
`The endpoint '${endpoint}' is currently disabled. Go to https://discord.gg/yyW389c for support.`,
34+
),
35+
);
36+
}
37+
1938
/**
2039
* Extract API key from request headers.
2140
*
@@ -49,6 +68,7 @@ const authorize = requiredRole => async (req, res, next) => {
4968
const updateData = {
5069
$inc: {
5170
req_quota: userData && userData.req_quota > 0 ? -1 : 0,
71+
req_consumed: userData && userData.req_quota > 0 ? 1 : 0,
5272
req_count: userData ? 1 : 0,
5373
},
5474
};
@@ -92,6 +112,11 @@ const authorize = requiredRole => async (req, res, next) => {
92112
return next(createError(403, 'Insufficient privileges to access this endpoint.'));
93113
}
94114

115+
/**
116+
* Log the user request.
117+
*/
118+
await logUserRequest(userData._id, endpoint);
119+
95120
/**
96121
* Increment system stats for successful requests.
97122
*/
@@ -113,6 +138,46 @@ const authorize = requiredRole => async (req, res, next) => {
113138
}
114139
};
115140

141+
/**
142+
* Helper function to extract endpoint from the request URL.
143+
*
144+
* @param {string} url - The request URL.
145+
* @returns {string} - The extracted endpoint.
146+
*/
147+
const getEndpointFromUrl = url => {
148+
const urlSegments = url.split('/');
149+
return urlSegments[urlSegments.length - 1]; // Last segment is assumed to be the endpoint
150+
};
151+
152+
/**
153+
* Helper function to check if the endpoint is enabled in the Stats collection.
154+
*
155+
* @param {string} endpoint - The endpoint to check.
156+
* @returns {Promise<boolean>} - Promise resolving to true if enabled, false otherwise.
157+
*/
158+
const isEndpointEnabledInStats = async endpoint => {
159+
try {
160+
// Assuming 'Stats' is the correct model for endpoint settings
161+
const settings = await Stats.findOne();
162+
163+
// Handle case where settings are not found
164+
if (!settings) {
165+
return false;
166+
}
167+
168+
// Check if endpoint exists in settings and isEnabled is defined
169+
if (settings[endpoint] && typeof settings[endpoint].isEnabled !== 'undefined') {
170+
return settings[endpoint].isEnabled;
171+
}
172+
173+
// Default to true if isEnabled is not defined or endpoint doesn't exist
174+
return true;
175+
} catch (error) {
176+
console.error('Error fetching endpoint settings:', error);
177+
return true;
178+
}
179+
};
180+
116181
/**
117182
* Increment the specified statistics in the system stats collection.
118183
*
@@ -125,4 +190,28 @@ const incrementSystemStats = async stats => {
125190
await Stats.findByIdAndUpdate({ _id: 'systemstats' }, { $inc: stats });
126191
};
127192

193+
/**
194+
* Log the number of requests made by a user to a specific endpoint.
195+
*
196+
* @param {string} userId - The ID of the user.
197+
* @param {string} endpoint - The endpoint being accessed.
198+
* @returns {Promise<void>} - Resolves when the log is updated.
199+
*/
200+
const logUserRequest = async (userId, endpoint) => {
201+
try {
202+
// Find the user and update the request count for the specific endpoint
203+
await Users.findByIdAndUpdate(
204+
userId,
205+
{
206+
$inc: {
207+
[`statistics.requests.${endpoint}`]: 1,
208+
},
209+
},
210+
{ new: true, upsert: true }, // Create a new document if it doesn't exist
211+
);
212+
} catch (error) {
213+
console.error('Error logging user request:', error);
214+
}
215+
};
216+
128217
export default authorize;

src/middlewares/rateLimit.js

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,11 @@
11
import rateLimit from 'express-rate-limit';
2-
import Users from '../models/schemas/User.js';
32

43
/**
54
* @function createRateLimiter
65
* @description Create and return the rate limiter middleware.
76
* @returns {Function} Express middleware for rate limiting.
8-
*
9-
* @example
10-
* // Basic usage
11-
* const limiter = createRateLimiter();
12-
* app.use('/api/route', limiter);
13-
*
14-
* @example
15-
* // Customized options
16-
* const customOptions = {
17-
* windowMs: 15 * 60 * 1000, // 15 minutes
18-
* max: 100, // limit each IP to 100 requests per windowMs
19-
* message: 'Too many requests from this IP, please try again after a few minutes.',
20-
* };
21-
* const customLimiter = createRateLimiter(customOptions);
22-
* app.use('/api/customRoute', customLimiter);
237
*/
248
const createRateLimiter = () => {
25-
/**
26-
* Default rate limiting options.
27-
* @typedef {Object} RateLimitOptions
28-
* @property {number} [windowMs=60000] - The time window for which the requests are checked/metered (in milliseconds).
29-
* @property {number} [max=20] - The maximum number of allowed requests within the windowMs time frame.
30-
* @property {Object} message - The message sent in the response when the limit is exceeded.
31-
* @property {number} [message.status=429] - The HTTP status code to be set in the response.
32-
* @property {string} [message.message='You've exhausted your ratelimit, please try again later.'] - The message to be sent in the response.
33-
*/
349
const defaultOptions = {
3510
windowMs: 60 * 1000, // 1 minute
3611
max: 20, // Default rate limit
@@ -40,38 +15,7 @@ const createRateLimiter = () => {
4015
},
4116
};
4217

43-
// Create rate limiter middleware with default options
44-
const limiter = rateLimit(defaultOptions);
45-
46-
/**
47-
* Express middleware function for rate limiting.
48-
* @param {Object} req - Express request object.
49-
* @param {Object} res - Express response object.
50-
* @param {Function} next - Express next function.
51-
*/
52-
return async (req, res, next) => {
53-
try {
54-
// Extract token from request headers
55-
const token = req.headers.authorization;
56-
57-
// Find user data from the database based on token
58-
const user = await Users.findOne({ token });
59-
60-
// Override default rate limit if user's rate limit is defined
61-
if (user && user.rateLimit) {
62-
limiter.options.max = user.rateLimit;
63-
}
64-
65-
// Apply rate limiting
66-
limiter(req, res, next);
67-
} catch (error) {
68-
// Handle errors when fetching user data
69-
console.error('Error fetching user data:', error.message);
70-
71-
// Apply rate limiting as a fallback
72-
limiter(req, res, next);
73-
}
74-
};
18+
return rateLimit(defaultOptions);
7519
};
7620

7721
export default createRateLimiter;

src/models/schemas/Stat.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,28 @@ const { Schema, model } = mongoose;
33

44
const StatSchema = new Schema({
55
_id: { type: String, required: true, default: 'system' },
6+
dashboard: {
7+
isEnabled: { type: Boolean, default: true },
8+
},
9+
registrations: {
10+
isEnabled: { type: Boolean, default: true },
11+
},
12+
login: {
13+
isEnabled: { type: Boolean, default: true },
14+
},
15+
tokenReset: {
16+
isEnabled: { type: Boolean, default: true },
17+
},
18+
quote: {
19+
isEnabled: { type: Boolean, default: true },
20+
},
621
total_requests: { type: Number, default: 0 },
722
endpoints_requests: { type: Number, default: 0 },
823
failed_requests: { type: Number, default: 0 },
924
success_requests: { type: Number, default: 0 },
1025
banned_requests: { type: Number, default: 0 },
1126
daily_requests: { type: Number, default: 0 },
27+
stats: { type: Number, default: 0 },
1228
run: { type: Number, default: 0 },
1329
sad: { type: Number, default: 0 },
1430
shoot: { type: Number, default: 0 },

src/models/schemas/User.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const UserSchema = new mongoose.Schema({
7676
* @type {number}
7777
* @default 500
7878
*/
79-
req_quota: { type: Number, default: 500 },
79+
req_quota: { type: Number, required: true, default: 500 },
8080

8181
/**
8282
* Number of requests made by the user.
@@ -85,6 +85,13 @@ const UserSchema = new mongoose.Schema({
8585
*/
8686
req_count: { type: Number, default: 0 },
8787

88+
/**
89+
* Number of requests consumed by the user.
90+
* @type {number}
91+
* @default 0
92+
*/
93+
req_consumed: { type: Number, default: 0 },
94+
8895
/**
8996
* Date and time when the user account was created.
9097
* @type {Date}
@@ -108,6 +115,54 @@ const UserSchema = new mongoose.Schema({
108115
* @default ['user']
109116
*/
110117
roles: { type: [String], default: ['user'] },
118+
119+
/**
120+
* Subscription or plan type.
121+
* @type {string}
122+
*/
123+
planType: { type: String, default: 'free' },
124+
125+
/**
126+
* Subscription start date.
127+
* @type {Date}
128+
*/
129+
subscriptionStart: { type: Date },
130+
131+
/**
132+
* Subscription end date.
133+
* @type {Date}
134+
*/
135+
subscriptionEnd: { type: Date },
136+
137+
/**
138+
* Subscription status.
139+
* @type {string}
140+
* @enum ['active', 'expired', 'canceled', 'pending', 'suspended', 'trial', 'renewal due', 'grace period']
141+
* @default 'active'
142+
*/
143+
subscriptionStatus: {
144+
type: String,
145+
enum: ['active', 'expired', 'canceled', 'pending', 'suspended', 'trial', 'renewal due', 'grace period'],
146+
default: 'active',
147+
},
148+
149+
/**
150+
* Metadata for subscription.
151+
* @type {object}
152+
*/
153+
subscriptionMetadata: { type: Object },
154+
155+
/**
156+
* Object to store the count of requests made to each endpoint by the user.
157+
* @type {Object}
158+
*/
159+
statistics: {
160+
requests: {
161+
type: Map,
162+
of: Number,
163+
default: {},
164+
},
165+
},
111166
});
112167

113168
/**

src/routes/v4/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,22 @@ import yesRoutes from './interactions/yes.js';
11921192
*/
11931193
router.use('/yes', yesRoutes);
11941194

1195+
import statsRoutes from './internal/stats.js';
1196+
1197+
/**
1198+
* @api {use} v4/stats Use Stats Routes
1199+
* @apiDescription Mount the stats-related routes for handling interactions.
1200+
* @apiName UseStatsRoutes
1201+
* @apiGroup Routes
1202+
*
1203+
* @apiSuccess {Object} routes Stats-related routes mounted on the parent router.
1204+
*
1205+
* @function createStatsRoutes
1206+
* @description Creates and returns a set of routes for handling interactions related to Stats.
1207+
* @returns {Object} Stats-related routes.
1208+
*/
1209+
router.use('/stats', statsRoutes);
1210+
11951211
/**
11961212
* Exporting the router for use in other parts of the application.
11971213
* @exports {Router} router - Express Router instance with mounted routes.

0 commit comments

Comments
 (0)