···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`
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) > 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:
2727+ - Edit: `/admin.html?token=...&action=edit&level=123`
2828+ - Delete: `/admin.html?token=...&action=delete&level=123`
3129 After a successful token-based edit/delete, the page attempts to call `window.close()` (some browsers only allow this for windows opened by script).
32303331## Getting Started
···8381- **URL:** `/api/levels`
8482- **Method:** `POST`
8583- **Content Types:**
8686- - `application/octet-stream`
8484+ - `application/octet-stream`
8785- **URL Params:** None
8886- **Query Params:** (for octet-stream)
8989- - `author`: Author name
9090- - `turnstileResponse`: Captcha verification token (if enabled)
8787+ - `author`: Author name
8888+ - `turnstileResponse`: Captcha verification token (if enabled)
9189- **Notes:** Only IZL3 is supported (v2 is deprecated).
9290- **Request Body:** Raw binary level data (`.izl3`), sent as the request body.
9391- **Success Response:**
9494- - **Code:** 201
9595- - **Content:**
9292+ - **Code:** 201
9393+ - **Content:**
96949797- ```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- ```
9595+ ```json
9696+ {
9797+ "id": 123,
9898+ "name": "Level Name",
9999+ "author": "Author Name",
100100+ "created_at": 1714680000,
101101+ "sun": 100,
102102+ "is_water": true,
103103+ "version": 3
104104+ }
105105+ ```
108106109109- Note: `is_water` is stored as `0/1` in the database and is returned as `0/1` in list/detail endpoints.
107107+ Note: `is_water` is stored as `0/1` in the database and is returned as `0/1` in list/detail endpoints.
110108111109- **Error Responses:**
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" }`
110110+ - **Code:** 400
111111+ - **Content:** `{ "error": "Missing required fields" }`
112112+ - **Code:** 400
113113+ - **Content:** `{ "error": "Content contains inappropriate language or content" }`
114114+ - **Code:** 400
115115+ - **Content:** `{ "error": "Captcha verification required" }`
116116+ - **Code:** 400
117117+ - **Content:** `{ "error": "Invalid captcha" }`
118118+ - **Code:** 500
119119+ - **Content:** `{ "error": "Failed to upload level" }`
122120123121##### List Levels
124122···126124- **Method:** `GET`
127125- **URL Params:** None
128126- **Query Params:**
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`.
127127+ - `page`: Page number (default: 1)
128128+ - `limit`: Results per page (default: 10)
129129+ - `author`: Filter by author name (partial match)
130130+ - `is_water`: Filter by water levels ("true"/"false")
131131+ - `version`: Filter by level version (currently always `3`; reserved for future versions)
132132+ - `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.
133133+ - `reversed_order`: Reverse the sort order (`true` or `1`). By default, sorting is descending.
134134+ - `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`.
137135- **Success Response:**
138138- - **Code:** 200
139139- - **Content:**
136136+ - **Code:** 200
137137+ - **Content:**
140138141141- ```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- ```
139139+ ```json
140140+ {
141141+ "levels": [
142142+ {
143143+ "id": 123,
144144+ "name": "Level Name",
145145+ "author": "Author Name",
146146+ "created_at": 1714680000,
147147+ "sun": 100,
148148+ "is_water": 1,
149149+ "favorites": 5,
150150+ "plays": 10,
151151+ "difficulty": 7,
152152+ "thumbnail": [[0, 10, 10, 40, 40, 1]],
153153+ "version": 3
154154+ }
155155+ ],
156156+ "pagination": {
157157+ "total": 50,
158158+ "page": 1,
159159+ "limit": 10,
160160+ "pages": 5
161161+ }
162162+ }
163163+ ```
166164167165- **Error Response:**
168168- - **Code:** 401
169169- - **Content:** `{ "error": "Invalid token" }`
170170- - **Code:** 500
171171- - **Content:** `{ "error": "Failed to list levels" }`
166166+ - **Code:** 401
167167+ - **Content:** `{ "error": "Invalid token" }`
168168+ - **Code:** 500
169169+ - **Content:** `{ "error": "Failed to list levels" }`
172170173171##### Get Level Details
174172175173- **URL:** `/api/levels/:id`
176174- **Method:** `GET`
177175- **URL Params:**
178178- - `id`: Level ID
176176+ - `id`: Level ID
179177- **Success Response:**
180180- - **Code:** 200
181181- - **Content:**
178178+ - **Code:** 200
179179+ - **Content:**
182180183183- ```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- ```
181181+ ```json
182182+ {
183183+ "id": 123,
184184+ "name": "Level Name",
185185+ "author": "Author Name",
186186+ "created_at": 1714680000,
187187+ "sun": 100,
188188+ "is_water": 1,
189189+ "favorites": 5,
190190+ "plays": 10,
191191+ "difficulty": 7,
192192+ "thumbnail": null,
193193+ "version": 3
194194+ }
195195+ ```
198196199197- **Error Responses:**
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" }`
198198+ - **Code:** 400
199199+ - **Content:** `{ "error": "Invalid level ID" }`
200200+ - **Code:** 404
201201+ - **Content:** `{ "error": "Level not found" }`
202202+ - **Code:** 500
203203+ - **Content:** `{ "error": "Failed to get level" }`
206204207205##### Download Level
208206209207- **URL:** `/api/levels/:id/download`
210208- **Method:** `GET`
211209- **URL Params:**
212212- - `id`: Level ID
210210+ - `id`: Level ID
213211- **Success Response:**
214214- - **Code:** 200
215215- - **Content:** Binary file download with `.izl3` extension
212212+ - **Code:** 200
213213+ - **Content:** Binary file download with `.izl3` extension
216214- **Error Responses:**
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" }`
215215+ - **Code:** 400
216216+ - **Content:** `{ "error": "Invalid level ID" }`
217217+ - **Code:** 404
218218+ - **Content:** `{ "error": "Level not found" }` or `{ "error": "Level file not found" }`
219219+ - **Code:** 500
220220+ - **Content:** `{ "error": "Failed to download level" }`
223221224222#### Favorites
225223···228226- **URL:** `/api/levels/:id/favorite`
229227- **Method:** `POST`
230228- **URL Params:**
231231- - `id`: Level ID
229229+ - `id`: Level ID
232230- **Request Body:** None (this endpoint always toggles favorite on/off)
233231- **Success Response:**
234234- - **Code:** 200
235235- - **Content:** `{ "success": true, "level": { "id": 123, "favorites": 5, ... } }`
232232+ - **Code:** 200
233233+ - **Content:** `{ "success": true, "level": { "id": 123, "favorites": 5, ... } }`
236234- **Error Responses:**
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" }`
235235+ - **Code:** 400
236236+ - **Content:** `{ "error": "Invalid level ID" }`
237237+ - **Code:** 404
238238+ - **Content:** `{ "error": "Level not found" }`
239239+ - **Code:** 500
240240+ - **Content:** `{ "error": "Failed to favorite level" }`
243241 Note: Captcha verification is not required for favoriting.
244242245243#### Reporting
···249247- **URL:** `/api/levels/:id/report`
250248- **Method:** `POST`
251249- **URL Params:**
252252- - `id`: Level ID
250250+ - `id`: Level ID
253251- **Request Body:**
254252255253 ```json
···259257 ```
260258261259- **Behavior:**
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.
260260+ - If `USE_REPORTING=false`, this endpoint returns 404.
261261+ - If `DISCORD_REPORT_WEBHOOK_URL` is configured, the server sends the report to the Discord webhook (and attaches the level file if available).
262262+ - If no webhook is configured, the server still accepts the report and returns success.
265263- **Success Response:**
266266- - **Code:** 200
267267- - **Content:** `{ "success": true }`
264264+ - **Code:** 200
265265+ - **Content:** `{ "success": true }`
268266- **Error Responses:**
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" }`
267267+ - **Code:** 400
268268+ - **Content:** `{ "error": "Invalid input" }`
269269+ - **Code:** 404
270270+ - **Content:** `{ "error": "Level not found" }`
271271+ - **Code:** 500
272272+ - **Content:** `{ "error": "Failed to report level" }`
275273276274#### Configuration
277275···280278- **URL:** `/api/config`
281279- **Method:** `GET`
282280- **Success Response:**
283283- - **Code:** 200
284284- - **Content:**
281281+ - **Code:** 200
282282+ - **Content:**
285283286286- ```json
287287- {
288288- "turnstileEnabled": true,
289289- "turnstileSiteKey": "0x0000000000000000000000",
290290- "moderationEnabled": true
291291- }
292292- ```
284284+ ```json
285285+ {
286286+ "turnstileEnabled": true,
287287+ "turnstileSiteKey": "0x0000000000000000000000",
288288+ "moderationEnabled": true
289289+ }
290290+ ```
293291294292## Environment Variables
295293
+37-37
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- - 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
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
991010- [x] _(Removed in favor of NGINX)_ ~~**Fix SSL/HTTPS Implementation**: The current SSL implementation is incomplete and non-functional~~
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
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
14141515## Medium Priority
16161717- [x] **Environment Configuration Management**
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
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
21212222- [x] **API Security Improvements**
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)~~
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)~~
27272828- [ ] **Database Improvements**
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.)
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.)
33333434- [ ] **Error Handling & Logging**
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
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
39394040## Low Priority
41414242- [x] **Code Quality & Maintenance**
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)~~
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)~~
47474848- [ ] **Feature Enhancements**
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
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
53535454- [ ] **Performance Optimizations**
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
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
59596060- [ ] **Deployment & DevOps**
6161- - Add Docker containerization
6262- - Create deployment scripts
6363- - Add health check endpoint (`/api/health`)
6464- - Implement graceful shutdown handling
6161+ - Add Docker containerization
6262+ - Create deployment scripts
6363+ - Add health check endpoint (`/api/health`)
6464+ - Implement graceful shutdown handling