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.

fix github action and shut up markdownlint Original commit: https://github.com/ROBlNET13/pvzm-backend/commit/11a5ef885e1f28e445221c1b5bd0d6567e5a226f

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

+172 -174
+135 -137
README.md
··· 20 20 21 21 - **Access**: Navigate to `/admin.html` or click the "Admin Dashboard" link on the main page 22 22 - **Features**: 23 - - View all levels with pagination 24 - - Search levels by name, author, or ID 25 - - Edit level properties (name, author, sun, water status, difficulty, statistics) 26 - - Delete levels (including related files and database entries) 27 - > Authentication: The admin UI supports optional GitHub OAuth. If `USE_GITHUB_AUTH=true`, users must sign in with GitHub and be included in `GITHUB_ALLOWED_USERS` to access admin endpoints. If `USE_GITHUB_AUTH=false`, the admin endpoints are not protected (not recommended in production). 28 - > The admin dashboard also supports one-time-token links for a single edit/delete action: 29 - - Edit: `/admin.html?token=...&action=edit&level=123` 30 - - Delete: `/admin.html?token=...&action=delete&level=123` 23 + - View all levels with pagination 24 + - Search levels by name, author, or ID 25 + - Edit level properties (name, author, sun, water status, difficulty, statistics) 26 + - Delete levels (including related files and database entries) > Authentication: The admin UI supports optional GitHub OAuth. If `USE_GITHUB_AUTH=true`, users must sign in with GitHub and be included in `GITHUB_ALLOWED_USERS` to access admin endpoints. If `USE_GITHUB_AUTH=false`, the admin endpoints are not protected (not recommended in production). > The admin dashboard also supports one-time-token links for a single edit/delete action: 27 + - Edit: `/admin.html?token=...&action=edit&level=123` 28 + - Delete: `/admin.html?token=...&action=delete&level=123` 31 29 After a successful token-based edit/delete, the page attempts to call `window.close()` (some browsers only allow this for windows opened by script). 32 30 33 31 ## Getting Started ··· 83 81 - **URL:** `/api/levels` 84 82 - **Method:** `POST` 85 83 - **Content Types:** 86 - - `application/octet-stream` 84 + - `application/octet-stream` 87 85 - **URL Params:** None 88 86 - **Query Params:** (for octet-stream) 89 - - `author`: Author name 90 - - `turnstileResponse`: Captcha verification token (if enabled) 87 + - `author`: Author name 88 + - `turnstileResponse`: Captcha verification token (if enabled) 91 89 - **Notes:** Only IZL3 is supported (v2 is deprecated). 92 90 - **Request Body:** Raw binary level data (`.izl3`), sent as the request body. 93 91 - **Success Response:** 94 - - **Code:** 201 95 - - **Content:** 92 + - **Code:** 201 93 + - **Content:** 96 94 97 - ```json 98 - { 99 - "id": 123, 100 - "name": "Level Name", 101 - "author": "Author Name", 102 - "created_at": 1714680000, 103 - "sun": 100, 104 - "is_water": true, 105 - "version": 3 106 - } 107 - ``` 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 + ``` 108 106 109 - 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. 110 108 111 109 - **Error Responses:** 112 - - **Code:** 400 113 - - **Content:** `{ "error": "Missing required fields" }` 114 - - **Code:** 400 115 - - **Content:** `{ "error": "Content contains inappropriate language or content" }` 116 - - **Code:** 400 117 - - **Content:** `{ "error": "Captcha verification required" }` 118 - - **Code:** 400 119 - - **Content:** `{ "error": "Invalid captcha" }` 120 - - **Code:** 500 121 - - **Content:** `{ "error": "Failed to upload level" }` 110 + - **Code:** 400 111 + - **Content:** `{ "error": "Missing required fields" }` 112 + - **Code:** 400 113 + - **Content:** `{ "error": "Content contains inappropriate language or content" }` 114 + - **Code:** 400 115 + - **Content:** `{ "error": "Captcha verification required" }` 116 + - **Code:** 400 117 + - **Content:** `{ "error": "Invalid captcha" }` 118 + - **Code:** 500 119 + - **Content:** `{ "error": "Failed to upload level" }` 122 120 123 121 ##### List Levels 124 122 ··· 126 124 - **Method:** `GET` 127 125 - **URL Params:** None 128 126 - **Query Params:** 129 - - `page`: Page number (default: 1) 130 - - `limit`: Results per page (default: 10) 131 - - `author`: Filter by author name (partial match) 132 - - `is_water`: Filter by water levels ("true"/"false") 133 - - `version`: Filter by level version (currently always `3`; reserved for future versions) 134 - - `sort`: Sorting mode. Default is by play count (`plays`). Use `recent` to sort by creation time (`created_at`) and `favorites` to sort by favorite count. 135 - - `reversed_order`: Reverse the sort order (`true` or `1`). By default, sorting is descending. 136 - - `token`: One-time token. If provided and valid, the response is filtered to the single level associated with that token (and pagination becomes `page=1`, `limit=1`). If the token is invalid, the endpoint returns `401`. 127 + - `page`: Page number (default: 1) 128 + - `limit`: Results per page (default: 10) 129 + - `author`: Filter by author name (partial match) 130 + - `is_water`: Filter by water levels ("true"/"false") 131 + - `version`: Filter by level version (currently always `3`; reserved for future versions) 132 + - `sort`: Sorting mode. Default is by play count (`plays`). Use `recent` to sort by creation time (`created_at`) and `favorites` to sort by favorite count. 133 + - `reversed_order`: Reverse the sort order (`true` or `1`). By default, sorting is descending. 134 + - `token`: One-time token. If provided and valid, the response is filtered to the single level associated with that token (and pagination becomes `page=1`, `limit=1`). If the token is invalid, the endpoint returns `401`. 137 135 - **Success Response:** 138 - - **Code:** 200 139 - - **Content:** 136 + - **Code:** 200 137 + - **Content:** 140 138 141 - ```json 142 - { 143 - "levels": [ 144 - { 145 - "id": 123, 146 - "name": "Level Name", 147 - "author": "Author Name", 148 - "created_at": 1714680000, 149 - "sun": 100, 150 - "is_water": 1, 151 - "favorites": 5, 152 - "plays": 10, 153 - "difficulty": 7, 154 - "thumbnail": [[0, 10, 10, 40, 40, 1]], 155 - "version": 3 156 - } 157 - ], 158 - "pagination": { 159 - "total": 50, 160 - "page": 1, 161 - "limit": 10, 162 - "pages": 5 163 - } 164 - } 165 - ``` 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 + ``` 166 164 167 165 - **Error Response:** 168 - - **Code:** 401 169 - - **Content:** `{ "error": "Invalid token" }` 170 - - **Code:** 500 171 - - **Content:** `{ "error": "Failed to list levels" }` 166 + - **Code:** 401 167 + - **Content:** `{ "error": "Invalid token" }` 168 + - **Code:** 500 169 + - **Content:** `{ "error": "Failed to list levels" }` 172 170 173 171 ##### Get Level Details 174 172 175 173 - **URL:** `/api/levels/:id` 176 174 - **Method:** `GET` 177 175 - **URL Params:** 178 - - `id`: Level ID 176 + - `id`: Level ID 179 177 - **Success Response:** 180 - - **Code:** 200 181 - - **Content:** 178 + - **Code:** 200 179 + - **Content:** 182 180 183 - ```json 184 - { 185 - "id": 123, 186 - "name": "Level Name", 187 - "author": "Author Name", 188 - "created_at": 1714680000, 189 - "sun": 100, 190 - "is_water": 1, 191 - "favorites": 5, 192 - "plays": 10, 193 - "difficulty": 7, 194 - "thumbnail": null, 195 - "version": 3 196 - } 197 - ``` 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 + ``` 198 196 199 197 - **Error Responses:** 200 - - **Code:** 400 201 - - **Content:** `{ "error": "Invalid level ID" }` 202 - - **Code:** 404 203 - - **Content:** `{ "error": "Level not found" }` 204 - - **Code:** 500 205 - - **Content:** `{ "error": "Failed to get level" }` 198 + - **Code:** 400 199 + - **Content:** `{ "error": "Invalid level ID" }` 200 + - **Code:** 404 201 + - **Content:** `{ "error": "Level not found" }` 202 + - **Code:** 500 203 + - **Content:** `{ "error": "Failed to get level" }` 206 204 207 205 ##### Download Level 208 206 209 207 - **URL:** `/api/levels/:id/download` 210 208 - **Method:** `GET` 211 209 - **URL Params:** 212 - - `id`: Level ID 210 + - `id`: Level ID 213 211 - **Success Response:** 214 - - **Code:** 200 215 - - **Content:** Binary file download with `.izl3` extension 212 + - **Code:** 200 213 + - **Content:** Binary file download with `.izl3` extension 216 214 - **Error Responses:** 217 - - **Code:** 400 218 - - **Content:** `{ "error": "Invalid level ID" }` 219 - - **Code:** 404 220 - - **Content:** `{ "error": "Level not found" }` or `{ "error": "Level file not found" }` 221 - - **Code:** 500 222 - - **Content:** `{ "error": "Failed to download level" }` 215 + - **Code:** 400 216 + - **Content:** `{ "error": "Invalid level ID" }` 217 + - **Code:** 404 218 + - **Content:** `{ "error": "Level not found" }` or `{ "error": "Level file not found" }` 219 + - **Code:** 500 220 + - **Content:** `{ "error": "Failed to download level" }` 223 221 224 222 #### Favorites 225 223 ··· 228 226 - **URL:** `/api/levels/:id/favorite` 229 227 - **Method:** `POST` 230 228 - **URL Params:** 231 - - `id`: Level ID 229 + - `id`: Level ID 232 230 - **Request Body:** None (this endpoint always toggles favorite on/off) 233 231 - **Success Response:** 234 - - **Code:** 200 235 - - **Content:** `{ "success": true, "level": { "id": 123, "favorites": 5, ... } }` 232 + - **Code:** 200 233 + - **Content:** `{ "success": true, "level": { "id": 123, "favorites": 5, ... } }` 236 234 - **Error Responses:** 237 - - **Code:** 400 238 - - **Content:** `{ "error": "Invalid level ID" }` 239 - - **Code:** 404 240 - - **Content:** `{ "error": "Level not found" }` 241 - - **Code:** 500 242 - - **Content:** `{ "error": "Failed to favorite level" }` 235 + - **Code:** 400 236 + - **Content:** `{ "error": "Invalid level ID" }` 237 + - **Code:** 404 238 + - **Content:** `{ "error": "Level not found" }` 239 + - **Code:** 500 240 + - **Content:** `{ "error": "Failed to favorite level" }` 243 241 Note: Captcha verification is not required for favoriting. 244 242 245 243 #### Reporting ··· 249 247 - **URL:** `/api/levels/:id/report` 250 248 - **Method:** `POST` 251 249 - **URL Params:** 252 - - `id`: Level ID 250 + - `id`: Level ID 253 251 - **Request Body:** 254 252 255 253 ```json ··· 259 257 ``` 260 258 261 259 - **Behavior:** 262 - - If `USE_REPORTING=false`, this endpoint returns 404. 263 - - If `DISCORD_REPORT_WEBHOOK_URL` is configured, the server sends the report to the Discord webhook (and attaches the level file if available). 264 - - If no webhook is configured, the server still accepts the report and returns success. 260 + - If `USE_REPORTING=false`, this endpoint returns 404. 261 + - If `DISCORD_REPORT_WEBHOOK_URL` is configured, the server sends the report to the Discord webhook (and attaches the level file if available). 262 + - If no webhook is configured, the server still accepts the report and returns success. 265 263 - **Success Response:** 266 - - **Code:** 200 267 - - **Content:** `{ "success": true }` 264 + - **Code:** 200 265 + - **Content:** `{ "success": true }` 268 266 - **Error Responses:** 269 - - **Code:** 400 270 - - **Content:** `{ "error": "Invalid input" }` 271 - - **Code:** 404 272 - - **Content:** `{ "error": "Level not found" }` 273 - - **Code:** 500 274 - - **Content:** `{ "error": "Failed to report level" }` 267 + - **Code:** 400 268 + - **Content:** `{ "error": "Invalid input" }` 269 + - **Code:** 404 270 + - **Content:** `{ "error": "Level not found" }` 271 + - **Code:** 500 272 + - **Content:** `{ "error": "Failed to report level" }` 275 273 276 274 #### Configuration 277 275 ··· 280 278 - **URL:** `/api/config` 281 279 - **Method:** `GET` 282 280 - **Success Response:** 283 - - **Code:** 200 284 - - **Content:** 281 + - **Code:** 200 282 + - **Content:** 285 283 286 - ```json 287 - { 288 - "turnstileEnabled": true, 289 - "turnstileSiteKey": "0x0000000000000000000000", 290 - "moderationEnabled": true 291 - } 292 - ``` 284 + ```json 285 + { 286 + "turnstileEnabled": true, 287 + "turnstileSiteKey": "0x0000000000000000000000", 288 + "moderationEnabled": true 289 + } 290 + ``` 293 291 294 292 ## Environment Variables 295 293
+37 -37
TODO.md
··· 3 3 ## High Priority 4 4 5 5 - [x] **Separate Test UI and Admin UI Controls**: There should be a way to disable the test UI without also disabling the admin UI 6 - - Add `USE_TEST_UI` environment variable to control access to `/index.html` test interface 7 - - Keep `USE_PUBLIC_FOLDER` for admin UI but add conditional routing for test interface 8 - - This would allow production deployments to disable testing while keeping admin functionality 6 + - Add `USE_TEST_UI` environment variable to control access to `/index.html` test interface 7 + - Keep `USE_PUBLIC_FOLDER` for admin UI but add conditional routing for test interface 8 + - This would allow production deployments to disable testing while keeping admin functionality 9 9 10 10 - [x] _(Removed in favor of NGINX)_ ~~**Fix SSL/HTTPS Implementation**: The current SSL implementation is incomplete and non-functional~~ 11 - - The SSL certificate and key are read but not actually used to create an HTTPS server 12 - - Need to implement proper HTTPS server with Express.js or migrate to native Deno HTTPS 13 - - Add proper SSL error handling and validation 11 + - The SSL certificate and key are read but not actually used to create an HTTPS server 12 + - Need to implement proper HTTPS server with Express.js or migrate to native Deno HTTPS 13 + - Add proper SSL error handling and validation 14 14 15 15 ## Medium Priority 16 16 17 17 - [x] **Environment Configuration Management** 18 - - Create a `.env.example` file with all available environment variables 19 - - Add environment variable validation on startup 20 - - Document all configuration options in README.md 18 + - Create a `.env.example` file with all available environment variables 19 + - Add environment variable validation on startup 20 + - Document all configuration options in README.md 21 21 22 22 - [x] **API Security Improvements** 23 - - Implement API key authentication for programmatic access 24 - - Add request size limits for file uploads 25 - - Consider adding CSRF protection for admin endpoints 26 - - _(Handled by Cloudflare)_ ~~Add rate limiting for API endpoints (especially `/api/levels` POST)~~ 23 + - Implement API key authentication for programmatic access 24 + - Add request size limits for file uploads 25 + - Consider adding CSRF protection for admin endpoints 26 + - _(Handled by Cloudflare)_ ~~Add rate limiting for API endpoints (especially `/api/levels` POST)~~ 27 27 28 28 - [ ] **Database Improvements** 29 - - Add database migrations system for schema changes 30 - - Implement database connection pooling 31 - - Add database backup/restore functionality 32 - - Add indexes for better query performance (author, created_at, etc.) 29 + - Add database migrations system for schema changes 30 + - Implement database connection pooling 31 + - Add database backup/restore functionality 32 + - Add indexes for better query performance (author, created_at, etc.) 33 33 34 34 - [ ] **Error Handling & Logging** 35 - - Implement structured logging (JSON format) 36 - - Add error tracking/monitoring integration 37 - - Improve error messages for better debugging 38 - - Add request/response logging middleware 35 + - Implement structured logging (JSON format) 36 + - Add error tracking/monitoring integration 37 + - Improve error messages for better debugging 38 + - Add request/response logging middleware 39 39 40 40 ## Low Priority 41 41 42 42 - [x] **Code Quality & Maintenance** 43 - - Split main.ts into separate modules (routes, middleware, database, etc.) 44 - - Add TypeScript strict mode configuration 45 - - Implement unit tests for core functionality 46 - - _(Decided against: API should not be public. README.md has instructions for the API.)_ ~~Add API documentation (OpenAPI/Swagger)~~ 43 + - Split main.ts into separate modules (routes, middleware, database, etc.) 44 + - Add TypeScript strict mode configuration 45 + - Implement unit tests for core functionality 46 + - _(Decided against: API should not be public. README.md has instructions for the API.)_ ~~Add API documentation (OpenAPI/Swagger)~~ 47 47 48 48 - [ ] **Feature Enhancements** 49 - - Add level search by tags/categories 50 - - Implement level comments/reviews system 51 - - Add user profiles and level collections 52 - - Add level statistics and analytics dashboard 49 + - Add level search by tags/categories 50 + - Implement level comments/reviews system 51 + - Add user profiles and level collections 52 + - Add level statistics and analytics dashboard 53 53 54 54 - [ ] **Performance Optimizations** 55 - - Implement response caching for level listings 56 - - Add CDN support for static files 57 - - Optimize database queries with prepared statements 58 - - Add pagination limits and validation 55 + - Implement response caching for level listings 56 + - Add CDN support for static files 57 + - Optimize database queries with prepared statements 58 + - Add pagination limits and validation 59 59 60 60 - [ ] **Deployment & DevOps** 61 - - Add Docker containerization 62 - - Create deployment scripts 63 - - Add health check endpoint (`/api/health`) 64 - - Implement graceful shutdown handling 61 + - Add Docker containerization 62 + - Create deployment scripts 63 + - Add health check endpoint (`/api/health`) 64 + - Implement graceful shutdown handling