A Deno-powered backend service for Plants vs. Zombies: MODDED. [Read-only GitHub mirror] docs.pvzm.net
express typescript expressjs plant deno jspvz pvzm game online backend plants-vs-zombies zombie javascript plants modded vs plantsvszombies openapi pvz noads
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Format "add rate limiting" Original commit: https://github.com/ROBlNET13/pvzm-backend/commit/9a441d7a24a2cda4d8a223413b7c78fe0af2ade4

Co-authored-by: ClaytonTDM <clay@clay.rip>

+66 -68
+59 -59
README.md
··· 92 92 - **Code:** 201 93 93 - **Content:** 94 94 95 - ```json 96 - { 97 - "id": 123, 98 - "name": "Level Name", 99 - "author": "Author Name", 100 - "created_at": 1714680000, 101 - "sun": 100, 102 - "is_water": true, 103 - "version": 3 104 - } 105 - ``` 95 + ```json 96 + { 97 + "id": 123, 98 + "name": "Level Name", 99 + "author": "Author Name", 100 + "created_at": 1714680000, 101 + "sun": 100, 102 + "is_water": true, 103 + "version": 3 104 + } 105 + ``` 106 106 107 - Note: `is_water` is stored as `0/1` in the database and is returned as `0/1` in list/detail endpoints. 107 + Note: `is_water` is stored as `0/1` in the database and is returned as `0/1` in list/detail endpoints. 108 108 109 109 - **Error Responses:** 110 110 - **Code:** 400 ··· 136 136 - **Code:** 200 137 137 - **Content:** 138 138 139 - ```json 140 - { 141 - "levels": [ 142 - { 143 - "id": 123, 144 - "name": "Level Name", 145 - "author": "Author Name", 146 - "created_at": 1714680000, 147 - "sun": 100, 148 - "is_water": 1, 149 - "favorites": 5, 150 - "plays": 10, 151 - "difficulty": 7, 152 - "thumbnail": [[0, 10, 10, 40, 40, 1]], 153 - "version": 3 154 - } 155 - ], 156 - "pagination": { 157 - "total": 50, 158 - "page": 1, 159 - "limit": 10, 160 - "pages": 5 161 - } 162 - } 163 - ``` 139 + ```json 140 + { 141 + "levels": [ 142 + { 143 + "id": 123, 144 + "name": "Level Name", 145 + "author": "Author Name", 146 + "created_at": 1714680000, 147 + "sun": 100, 148 + "is_water": 1, 149 + "favorites": 5, 150 + "plays": 10, 151 + "difficulty": 7, 152 + "thumbnail": [[0, 10, 10, 40, 40, 1]], 153 + "version": 3 154 + } 155 + ], 156 + "pagination": { 157 + "total": 50, 158 + "page": 1, 159 + "limit": 10, 160 + "pages": 5 161 + } 162 + } 163 + ``` 164 164 165 165 - **Error Response:** 166 166 - **Code:** 401 ··· 178 178 - **Code:** 200 179 179 - **Content:** 180 180 181 - ```json 182 - { 183 - "id": 123, 184 - "name": "Level Name", 185 - "author": "Author Name", 186 - "created_at": 1714680000, 187 - "sun": 100, 188 - "is_water": 1, 189 - "favorites": 5, 190 - "plays": 10, 191 - "difficulty": 7, 192 - "thumbnail": null, 193 - "version": 3 194 - } 195 - ``` 181 + ```json 182 + { 183 + "id": 123, 184 + "name": "Level Name", 185 + "author": "Author Name", 186 + "created_at": 1714680000, 187 + "sun": 100, 188 + "is_water": 1, 189 + "favorites": 5, 190 + "plays": 10, 191 + "difficulty": 7, 192 + "thumbnail": null, 193 + "version": 3 194 + } 195 + ``` 196 196 197 197 - **Error Responses:** 198 198 - **Code:** 400 ··· 281 281 - **Code:** 200 282 282 - **Content:** 283 283 284 - ```json 285 - { 286 - "turnstileEnabled": true, 287 - "turnstileSiteKey": "0x0000000000000000000000", 288 - "moderationEnabled": true 289 - } 290 - ``` 284 + ```json 285 + { 286 + "turnstileEnabled": true, 287 + "turnstileSiteKey": "0x0000000000000000000000", 288 + "moderationEnabled": true 289 + } 290 + ``` 291 291 292 292 ## Environment Variables 293 293
+7 -9
modules/server/routes/levels.ts
··· 54 54 const timestamps = apiLevelsRateLimitByIp.get(clientIP) ?? []; 55 55 pruneOldestTimestamps(timestamps, nowMs - API_LEVELS_WINDOW_MS); 56 56 if (timestamps.length >= API_LEVELS_LIMIT) { 57 - const retryAfterSeconds = timestamps.length === 0 58 - ? Math.ceil(API_LEVELS_WINDOW_MS / 1000) 59 - : Math.ceil((timestamps[0] + API_LEVELS_WINDOW_MS - nowMs) / 1000); 57 + const retryAfterSeconds = 58 + timestamps.length === 0 ? Math.ceil(API_LEVELS_WINDOW_MS / 1000) : Math.ceil((timestamps[0] + API_LEVELS_WINDOW_MS - nowMs) / 1000); 60 59 setRetryAfter(res, retryAfterSeconds); 61 60 return res.status(429).json({ 62 61 error: "Rate limit exceeded", ··· 108 107 const lastUploadMs = uploadRateLimitByIp.get(clientIP) ?? 0; 109 108 const elapsedMs = nowMs - lastUploadMs; 110 109 if (elapsedMs >= 0 && elapsedMs < UPLOAD_WINDOW_MS) { 111 - const retryAfterSeconds = Math.ceil( 112 - (UPLOAD_WINDOW_MS - elapsedMs) / 1000, 113 - ); 110 + const retryAfterSeconds = Math.ceil((UPLOAD_WINDOW_MS - elapsedMs) / 1000); 114 111 res.setHeader("Retry-After", String(retryAfterSeconds)); 115 112 return res.status(429).json({ 116 113 error: "Rate limit exceeded", ··· 616 613 const favoriteTimestamps = favoriteRateLimitByIp.get(clientIP) ?? []; 617 614 pruneOldestTimestamps(favoriteTimestamps, nowMs - FAVORITE_WINDOW_MS); 618 615 if (favoriteTimestamps.length >= FAVORITE_LIMIT) { 619 - const retryAfterSeconds = favoriteTimestamps.length === 0 620 - ? Math.ceil(FAVORITE_WINDOW_MS / 1000) 621 - : Math.ceil((favoriteTimestamps[0] + FAVORITE_WINDOW_MS - nowMs) / 1000); 616 + const retryAfterSeconds = 617 + favoriteTimestamps.length === 0 618 + ? Math.ceil(FAVORITE_WINDOW_MS / 1000) 619 + : Math.ceil((favoriteTimestamps[0] + FAVORITE_WINDOW_MS - nowMs) / 1000); 622 620 setRetryAfter(res, retryAfterSeconds); 623 621 return res.status(429).json({ 624 622 error: "Rate limit exceeded",