···11{
22 "MD013": false, // line length
33- "MD010": false // hard tabs
33+ "MD010": false, // hard tabs
44}
+4-5
COMMENTS.md
···55Here's a set of VSCode RegEx patterns to fix comment case. Keep in mind that these patterns are not perfect and may require you to exclude certain files manually (deno.lock comes to mind)
66771. Make comment lowercase:
88-99- - Find: `([^\S\r\n]+)//([^\S\r\n]*)([A-Z])`
1010- - Replace: `$1//$2\l$3`
88+ - Find: `([^\S\r\n]+)//([^\S\r\n]*)([A-Z])`
99+ - Replace: `$1//$2\l$3`
111012112. Fix inconsistencies (the above RegEx would change "DOM" to "dOM", this fixes that):
1313- - Find: `([^\S\r\n]+)//([^\S\r\n]*)([a-z])([A-Z])`
1414- - Replace: `$1//$2\u$3$4`
1212+ - Find: `([^\S\r\n]+)//([^\S\r\n]*)([a-z])([A-Z])`
1313+ - Replace: `$1//$2\u$3$4`
+144-148
README.md
···20202121- **Access**: Navigate to `/admin.html` or click the "Admin Dashboard" link on the main page
2222- **Features**:
2323- - View all levels with pagination
2424- - Search levels by name, author, or ID
2525- - Edit level properties (name, author, sun, water status, difficulty, statistics)
2626- - Delete levels (including related files and database entries)
2727- > 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).
2828- > The admin dashboard also supports one-time-token links for a single edit/delete action:
2929- - Edit: `/admin.html?token=...&action=edit&level=123`
3030- - Delete: `/admin.html?token=...&action=delete&level=123`
3131- After a successful token-based edit/delete, the page attempts to call `window.close()` (some browsers only allow this for windows opened by script).
2323+ - View all levels with pagination
2424+ - Search levels by name, author, or ID
2525+ - Edit level properties (name, author, sun, water status, difficulty, statistics)
2626+ - Delete levels (including related files and database entries)
2727+ > 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).
2828+ > The admin dashboard also supports one-time-token links for a single edit/delete action:
2929+ - Edit: `/admin.html?token=...&action=edit&level=123`
3030+ - Delete: `/admin.html?token=...&action=delete&level=123`
3131+ After a successful token-based edit/delete, the page attempts to call `window.close()` (some browsers only allow this for windows opened by script).
32323333## Getting Started
3434···8383- **URL:** `/api/levels`
8484- **Method:** `POST`
8585- **Content Types:**
8686- - `application/octet-stream`
8686+ - `application/octet-stream`
8787- **URL Params:** None
8888- **Query Params:** (for octet-stream)
8989- - `author`: Author name
9090- - `turnstileResponse`: Captcha verification token (if enabled)
8989+ - `author`: Author name
9090+ - `turnstileResponse`: Captcha verification token (if enabled)
9191- **Notes:** Only IZL3 is supported (v2 is deprecated).
9292- **Request Body:** Raw binary level data (`.izl3`), sent as the request body.
9393- **Success Response:**
9494+ - **Code:** 201
9595+ - **Content:**
94969595- - **Code:** 201
9696- - **Content:**
9797+ ```json
9898+ {
9999+ "id": 123,
100100+ "name": "Level Name",
101101+ "author": "Author Name",
102102+ "created_at": 1714680000,
103103+ "sun": 100,
104104+ "is_water": true,
105105+ "version": 3
106106+ }
107107+ ```
971089898- ```json
9999- {
100100- "id": 123,
101101- "name": "Level Name",
102102- "author": "Author Name",
103103- "created_at": 1714680000,
104104- "sun": 100,
105105- "is_water": true,
106106- "version": 3
107107- }
108108- ```
109109-110110- Note: `is_water` is stored as `0/1` in the database and is returned as `0/1` in list/detail endpoints.
109109+ Note: `is_water` is stored as `0/1` in the database and is returned as `0/1` in list/detail endpoints.
111110112111- **Error Responses:**
113113- - **Code:** 400
114114- - **Content:** `{ "error": "Missing required fields" }`
115115- - **Code:** 400
116116- - **Content:** `{ "error": "Content contains inappropriate language or content" }`
117117- - **Code:** 400
118118- - **Content:** `{ "error": "Captcha verification required" }`
119119- - **Code:** 400
120120- - **Content:** `{ "error": "Invalid captcha" }`
121121- - **Code:** 500
122122- - **Content:** `{ "error": "Failed to upload level" }`
112112+ - **Code:** 400
113113+ - **Content:** `{ "error": "Missing required fields" }`
114114+ - **Code:** 400
115115+ - **Content:** `{ "error": "Content contains inappropriate language or content" }`
116116+ - **Code:** 400
117117+ - **Content:** `{ "error": "Captcha verification required" }`
118118+ - **Code:** 400
119119+ - **Content:** `{ "error": "Invalid captcha" }`
120120+ - **Code:** 500
121121+ - **Content:** `{ "error": "Failed to upload level" }`
123122124123##### List Levels
125124···127126- **Method:** `GET`
128127- **URL Params:** None
129128- **Query Params:**
130130- - `page`: Page number (default: 1)
131131- - `limit`: Results per page (default: 10)
132132- - `author`: Filter by author name (partial match)
133133- - `is_water`: Filter by water levels ("true"/"false")
134134- - `version`: Filter by level version (currently always `3`; reserved for future versions)
135135- - `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.
136136- - `reversed_order`: Reverse the sort order (`true` or `1`). By default, sorting is descending.
137137- - `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`.
129129+ - `page`: Page number (default: 1)
130130+ - `limit`: Results per page (default: 10)
131131+ - `author`: Filter by author name (partial match)
132132+ - `is_water`: Filter by water levels ("true"/"false")
133133+ - `version`: Filter by level version (currently always `3`; reserved for future versions)
134134+ - `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.
135135+ - `reversed_order`: Reverse the sort order (`true` or `1`). By default, sorting is descending.
136136+ - `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`.
138137- **Success Response:**
138138+ - **Code:** 200
139139+ - **Content:**
139140140140- - **Code:** 200
141141- - **Content:**
142142-143143- ```json
144144- {
145145- "levels": [
146146- {
147147- "id": 123,
148148- "name": "Level Name",
149149- "author": "Author Name",
150150- "created_at": 1714680000,
151151- "sun": 100,
152152- "is_water": 1,
153153- "favorites": 5,
154154- "plays": 10,
155155- "difficulty": 7,
156156- "thumbnail": [[0, 10, 10, 40, 40, 1]],
157157- "version": 3
158158- }
159159- ],
160160- "pagination": {
161161- "total": 50,
162162- "page": 1,
163163- "limit": 10,
164164- "pages": 5
165165- }
166166- }
167167- ```
141141+ ```json
142142+ {
143143+ "levels": [
144144+ {
145145+ "id": 123,
146146+ "name": "Level Name",
147147+ "author": "Author Name",
148148+ "created_at": 1714680000,
149149+ "sun": 100,
150150+ "is_water": 1,
151151+ "favorites": 5,
152152+ "plays": 10,
153153+ "difficulty": 7,
154154+ "thumbnail": [[0, 10, 10, 40, 40, 1]],
155155+ "version": 3
156156+ }
157157+ ],
158158+ "pagination": {
159159+ "total": 50,
160160+ "page": 1,
161161+ "limit": 10,
162162+ "pages": 5
163163+ }
164164+ }
165165+ ```
168166169167- **Error Response:**
170170- - **Code:** 401
171171- - **Content:** `{ "error": "Invalid token" }`
172172- - **Code:** 500
173173- - **Content:** `{ "error": "Failed to list levels" }`
168168+ - **Code:** 401
169169+ - **Content:** `{ "error": "Invalid token" }`
170170+ - **Code:** 500
171171+ - **Content:** `{ "error": "Failed to list levels" }`
174172175173##### Get Level Details
176174177175- **URL:** `/api/levels/:id`
178176- **Method:** `GET`
179177- **URL Params:**
180180- - `id`: Level ID
178178+ - `id`: Level ID
181179- **Success Response:**
180180+ - **Code:** 200
181181+ - **Content:**
182182183183- - **Code:** 200
184184- - **Content:**
185185-186186- ```json
187187- {
188188- "id": 123,
189189- "name": "Level Name",
190190- "author": "Author Name",
191191- "created_at": 1714680000,
192192- "sun": 100,
193193- "is_water": 1,
194194- "favorites": 5,
195195- "plays": 10,
196196- "difficulty": 7,
197197- "thumbnail": null,
198198- "version": 3
199199- }
200200- ```
183183+ ```json
184184+ {
185185+ "id": 123,
186186+ "name": "Level Name",
187187+ "author": "Author Name",
188188+ "created_at": 1714680000,
189189+ "sun": 100,
190190+ "is_water": 1,
191191+ "favorites": 5,
192192+ "plays": 10,
193193+ "difficulty": 7,
194194+ "thumbnail": null,
195195+ "version": 3
196196+ }
197197+ ```
201198202199- **Error Responses:**
203203- - **Code:** 400
204204- - **Content:** `{ "error": "Invalid level ID" }`
205205- - **Code:** 404
206206- - **Content:** `{ "error": "Level not found" }`
207207- - **Code:** 500
208208- - **Content:** `{ "error": "Failed to get level" }`
200200+ - **Code:** 400
201201+ - **Content:** `{ "error": "Invalid level ID" }`
202202+ - **Code:** 404
203203+ - **Content:** `{ "error": "Level not found" }`
204204+ - **Code:** 500
205205+ - **Content:** `{ "error": "Failed to get level" }`
209206210207##### Download Level
211208212209- **URL:** `/api/levels/:id/download`
213210- **Method:** `GET`
214211- **URL Params:**
215215- - `id`: Level ID
212212+ - `id`: Level ID
216213- **Success Response:**
217217- - **Code:** 200
218218- - **Content:** Binary file download with `.izl3` extension
214214+ - **Code:** 200
215215+ - **Content:** Binary file download with `.izl3` extension
219216- **Error Responses:**
220220- - **Code:** 400
221221- - **Content:** `{ "error": "Invalid level ID" }`
222222- - **Code:** 404
223223- - **Content:** `{ "error": "Level not found" }` or `{ "error": "Level file not found" }`
224224- - **Code:** 500
225225- - **Content:** `{ "error": "Failed to download level" }`
217217+ - **Code:** 400
218218+ - **Content:** `{ "error": "Invalid level ID" }`
219219+ - **Code:** 404
220220+ - **Content:** `{ "error": "Level not found" }` or `{ "error": "Level file not found" }`
221221+ - **Code:** 500
222222+ - **Content:** `{ "error": "Failed to download level" }`
226223227224#### Favorites
228225···231228- **URL:** `/api/levels/:id/favorite`
232229- **Method:** `POST`
233230- **URL Params:**
234234- - `id`: Level ID
231231+ - `id`: Level ID
235232- **Request Body:** None (this endpoint always toggles favorite on/off)
236233- **Success Response:**
237237- - **Code:** 200
238238- - **Content:** `{ "success": true, "level": { "id": 123, "favorites": 5, ... } }`
234234+ - **Code:** 200
235235+ - **Content:** `{ "success": true, "level": { "id": 123, "favorites": 5, ... } }`
239236- **Error Responses:**
240240- - **Code:** 400
241241- - **Content:** `{ "error": "Invalid level ID" }`
242242- - **Code:** 404
243243- - **Content:** `{ "error": "Level not found" }`
244244- - **Code:** 500
245245- - **Content:** `{ "error": "Failed to favorite level" }`
246246- Note: Captcha verification is not required for favoriting.
237237+ - **Code:** 400
238238+ - **Content:** `{ "error": "Invalid level ID" }`
239239+ - **Code:** 404
240240+ - **Content:** `{ "error": "Level not found" }`
241241+ - **Code:** 500
242242+ - **Content:** `{ "error": "Failed to favorite level" }`
243243+ Note: Captcha verification is not required for favoriting.
247244248245#### Reporting
249246···252249- **URL:** `/api/levels/:id/report`
253250- **Method:** `POST`
254251- **URL Params:**
255255- - `id`: Level ID
252252+ - `id`: Level ID
256253- **Request Body:**
257254258258- ```json
259259- {
260260- "reason": "Brief description of the issue"
261261- }
262262- ```
255255+ ```json
256256+ {
257257+ "reason": "Brief description of the issue"
258258+ }
259259+ ```
263260264261- **Behavior:**
265265- - If `USE_REPORTING=false`, this endpoint returns 404.
266266- - If `DISCORD_REPORT_WEBHOOK_URL` is configured, the server sends the report to the Discord webhook (and attaches the level file if available).
267267- - If no webhook is configured, the server still accepts the report and returns success.
262262+ - If `USE_REPORTING=false`, this endpoint returns 404.
263263+ - If `DISCORD_REPORT_WEBHOOK_URL` is configured, the server sends the report to the Discord webhook (and attaches the level file if available).
264264+ - If no webhook is configured, the server still accepts the report and returns success.
268265- **Success Response:**
269269- - **Code:** 200
270270- - **Content:** `{ "success": true }`
266266+ - **Code:** 200
267267+ - **Content:** `{ "success": true }`
271268- **Error Responses:**
272272- - **Code:** 400
273273- - **Content:** `{ "error": "Invalid input" }`
274274- - **Code:** 404
275275- - **Content:** `{ "error": "Level not found" }`
276276- - **Code:** 500
277277- - **Content:** `{ "error": "Failed to report level" }`
269269+ - **Code:** 400
270270+ - **Content:** `{ "error": "Invalid input" }`
271271+ - **Code:** 404
272272+ - **Content:** `{ "error": "Level not found" }`
273273+ - **Code:** 500
274274+ - **Content:** `{ "error": "Failed to report level" }`
278275279276#### Configuration
280277···283280- **URL:** `/api/config`
284281- **Method:** `GET`
285282- **Success Response:**
286286-287287- - **Code:** 200
288288- - **Content:**
283283+ - **Code:** 200
284284+ - **Content:**
289285290290- ```json
291291- {
292292- "turnstileEnabled": true,
293293- "turnstileSiteKey": "0x0000000000000000000000",
294294- "moderationEnabled": true
295295- }
296296- ```
286286+ ```json
287287+ {
288288+ "turnstileEnabled": true,
289289+ "turnstileSiteKey": "0x0000000000000000000000",
290290+ "moderationEnabled": true
291291+ }
292292+ ```
297293298294## Environment Variables
299295
+37-44
TODO.md
···33## High Priority
4455- [x] **Separate Test UI and Admin UI Controls**: There should be a way to disable the test UI without also disabling the admin UI
66-77- - Add `USE_TEST_UI` environment variable to control access to `/index.html` test interface
88- - Keep `USE_PUBLIC_FOLDER` for admin UI but add conditional routing for test interface
99- - This would allow production deployments to disable testing while keeping admin functionality
66+ - Add `USE_TEST_UI` environment variable to control access to `/index.html` test interface
77+ - Keep `USE_PUBLIC_FOLDER` for admin UI but add conditional routing for test interface
88+ - This would allow production deployments to disable testing while keeping admin functionality
1091110- [x] _(Removed in favor of NGINX)_ ~~**Fix SSL/HTTPS Implementation**: The current SSL implementation is incomplete and non-functional~~
1212- - The SSL certificate and key are read but not actually used to create an HTTPS server
1313- - Need to implement proper HTTPS server with Express.js or migrate to native Deno HTTPS
1414- - Add proper SSL error handling and validation
1111+ - The SSL certificate and key are read but not actually used to create an HTTPS server
1212+ - Need to implement proper HTTPS server with Express.js or migrate to native Deno HTTPS
1313+ - Add proper SSL error handling and validation
15141615## Medium Priority
17161817- [x] **Environment Configuration Management**
1919-2020- - Create a `.env.example` file with all available environment variables
2121- - Add environment variable validation on startup
2222- - Document all configuration options in README.md
1818+ - Create a `.env.example` file with all available environment variables
1919+ - Add environment variable validation on startup
2020+ - Document all configuration options in README.md
23212422- [x] **API Security Improvements**
2525-2626- - Implement API key authentication for programmatic access
2727- - Add request size limits for file uploads
2828- - Consider adding CSRF protection for admin endpoints
2929- - _(Handled by Cloudflare)_ ~~Add rate limiting for API endpoints (especially `/api/levels` POST)~~
2323+ - Implement API key authentication for programmatic access
2424+ - Add request size limits for file uploads
2525+ - Consider adding CSRF protection for admin endpoints
2626+ - _(Handled by Cloudflare)_ ~~Add rate limiting for API endpoints (especially `/api/levels` POST)~~
30273128- [ ] **Database Improvements**
3232-3333- - Add database migrations system for schema changes
3434- - Implement database connection pooling
3535- - Add database backup/restore functionality
3636- - Add indexes for better query performance (author, created_at, etc.)
2929+ - Add database migrations system for schema changes
3030+ - Implement database connection pooling
3131+ - Add database backup/restore functionality
3232+ - Add indexes for better query performance (author, created_at, etc.)
37333834- [ ] **Error Handling & Logging**
3939- - Implement structured logging (JSON format)
4040- - Add error tracking/monitoring integration
4141- - Improve error messages for better debugging
4242- - Add request/response logging middleware
3535+ - Implement structured logging (JSON format)
3636+ - Add error tracking/monitoring integration
3737+ - Improve error messages for better debugging
3838+ - Add request/response logging middleware
43394440## Low Priority
45414642- [x] **Code Quality & Maintenance**
4747-4848- - Split main.ts into separate modules (routes, middleware, database, etc.)
4949- - Add TypeScript strict mode configuration
5050- - Implement unit tests for core functionality
5151- - _(Decided against: API should not be public. README.md has instructions for the API.)_ ~~Add API documentation (OpenAPI/Swagger)~~
4343+ - Split main.ts into separate modules (routes, middleware, database, etc.)
4444+ - Add TypeScript strict mode configuration
4545+ - Implement unit tests for core functionality
4646+ - _(Decided against: API should not be public. README.md has instructions for the API.)_ ~~Add API documentation (OpenAPI/Swagger)~~
52475348- [ ] **Feature Enhancements**
5454-5555- - Add level search by tags/categories
5656- - Implement level comments/reviews system
5757- - Add user profiles and level collections
5858- - Add level statistics and analytics dashboard
4949+ - Add level search by tags/categories
5050+ - Implement level comments/reviews system
5151+ - Add user profiles and level collections
5252+ - Add level statistics and analytics dashboard
59536054- [ ] **Performance Optimizations**
6161-6262- - Implement response caching for level listings
6363- - Add CDN support for static files
6464- - Optimize database queries with prepared statements
6565- - Add pagination limits and validation
5555+ - Implement response caching for level listings
5656+ - Add CDN support for static files
5757+ - Optimize database queries with prepared statements
5858+ - Add pagination limits and validation
66596760- [ ] **Deployment & DevOps**
6868- - Add Docker containerization
6969- - Create deployment scripts
7070- - Add health check endpoint (`/api/health`)
7171- - Implement graceful shutdown handling
6161+ - Add Docker containerization
6262+ - Create deployment scripts
6363+ - Add health check endpoint (`/api/health`)
6464+ - Implement graceful shutdown handling
···11-<!DOCTYPE html>
11+<!doctype html>
22<html lang="en">
33 <head>
44 <meta charset="UTF-8" />
···1515 </style>
1616 </head>
1717 <body>
1818- <main
1919- style="
2020- height: 100vh;
2121- display: flex;
2222- align-items: center;
2323- justify-content: center;
2424- "
2525- >
1818+ <main style="height: 100vh; display: flex; align-items: center; justify-content: center">
2619 <article style="max-width: 510px; text-align: center">
2720 <div class="error-icon">⚠</div>
2821 <h1>Access Denied</h1>
2929- <p>
3030- You are not authorized to access the admin dashboard. Only approved GitHub users
3131- can access this area.
3232- </p>
3333- <p>
3434- If you believe this is an error, please contact the system administrator to have
3535- your GitHub username added to the allowlist.
3636- </p>
2222+ <p>You are not authorized to access the admin dashboard. Only approved GitHub users can access this area.</p>
2323+ <p>If you believe this is an error, please contact the system administrator to have your GitHub username added to the allowlist.</p>
3724 <p>
3825 <a href="/" class="button">Return to Home</a>
3926 </p>