Repository: mistertfy64/UntitledPasswordFindingGame
Pull request #16: Administrator Problem Creation
Author: login: mistertfy64
Created: 2025-05-09T09:15:20Z | Merged: 2025-05-09T09:25:38Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Introduced an "Add Problem" page in the administrator dashboard, allowing administrators to create new problems with fields for name, statement (Markdown supported), ID, password, number, release timestamp, author, and visibility options.
- **Bug Fixes**
	- Updated the administrator dashboard to direct users to the new "Add Problem" page.
- **Chores**
	- Added support for storing the problem author when creating new problems.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/package-lock.json b/package-lock.json
index 164fbee..a3ed898 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,11 +16,13 @@
         "cookie-session": "^2.1.0",
         "copyfiles": "^2.4.1",
         "csrf-csrf": "^4.0.0",
+        "dompurify": "^3.2.5",
         "dotenv": "^16.5.0",
         "ejs": "^3.1.10",
         "express": "^5.1.0",
         "express-mongo-sanitize": "^2.2.0",
         "express-rate-limit": "^7.5.0",
+        "jsdom": "^26.1.0",
         "markdown-it": "^14.1.0",
         "markdown-it-sub": "^2.0.0",
         "mongoose": "^8.14.1",
@@ -30,11 +32,135 @@
         "@types/ejs": "^3.1.5",
         "@types/express": "^5.0.1",
         "@types/express-session": "^1.18.1",
+        "@types/jsdom": "^21.1.7",
         "@types/markdown-it": "^14.1.2",
         "@types/node": "^22.15.3",
         "typescript": "^5.8.3"
       }
     },
+    "node_modules/@asamuzakjp/css-color": {
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.7.tgz",
+      "integrity": "sha512-Ok5fYhtwdyJQmU1PpEv6Si7Y+A4cYb8yNM9oiIJC9TzXPMuN9fvdonKJqcnz9TbFqV6bQ8z0giRq0iaOpGZV2g==",
+      "license": "MIT",
+      "dependencies": {
+        "@csstools/css-calc": "^2.1.3",
+        "@csstools/css-color-parser": "^3.0.9",
+        "@csstools/css-parser-algorithms": "^3.0.4",
+        "@csstools/css-tokenizer": "^3.0.3",
+        "lru-cache": "^10.4.3"
+      }
+    },
+    "node_modules/@csstools/color-helpers": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
+      "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT-0",
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@csstools/css-calc": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz",
+      "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@csstools/css-parser-algorithms": "^3.0.4",
+        "@csstools/css-tokenizer": "^3.0.3"
+      }
+    },
+    "node_modules/@csstools/css-color-parser": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz",
+      "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@csstools/color-helpers": "^5.0.2",
+        "@csstools/css-calc": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@csstools/css-parser-algorithms": "^3.0.4",
+        "@csstools/css-tokenizer": "^3.0.3"
+      }
+    },
+    "node_modules/@csstools/css-parser-algorithms": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
+      "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@csstools/css-tokenizer": "^3.0.3"
+      }
+    },
+    "node_modules/@csstools/css-tokenizer": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
+      "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@dotenvx/dotenvx": {
       "version": "1.43.0",
       "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.43.0.tgz",
@@ -211,6 +337,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/jsdom": {
+      "version": "21.1.7",
+      "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz",
+      "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/tough-cookie": "*",
+        "parse5": "^7.0.0"
+      }
+    },
     "node_modules/@types/linkify-it": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
@@ -290,6 +428,20 @@
         "@types/send": "*"
       }
     },
+    "node_modules/@types/tough-cookie": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+      "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/trusted-types": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/@types/webidl-conversions": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@@ -820,6 +972,32 @@
         "http-errors": "^2.0.0"
       }
     },
+    "node_modules/cssstyle": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz",
+      "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@asamuzakjp/css-color": "^3.1.2",
+        "rrweb-cssom": "^0.8.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/data-urls": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+      "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-mimetype": "^4.0.0",
+        "whatwg-url": "^14.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/debug": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
@@ -837,6 +1015,12 @@
         }
       }
     },
+    "node_modules/decimal.js": {
+      "version": "10.5.0",
+      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
+      "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
+      "license": "MIT"
+    },
     "node_modules/delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -861,6 +1045,15 @@
         "node": ">=8"
       }
     },
+    "node_modules/dompurify": {
+      "version": "3.2.5",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz",
+      "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==",
+      "license": "(MPL-2.0 OR Apache-2.0)",
+      "optionalDependencies": {
+        "@types/trusted-types": "^2.0.7"
+      }
+    },
     "node_modules/dotenv": {
       "version": "16.5.0",
       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
@@ -1364,6 +1557,18 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/html-encoding-sniffer": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+      "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-encoding": "^3.1.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/http-errors": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -1380,6 +1585,28 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/http-proxy-agent": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.1.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/http-proxy-agent/node_modules/agent-base": {
+      "version": "7.1.3",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/https-proxy-agent": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -1458,6 +1685,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/is-potential-custom-element-name": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+      "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+      "license": "MIT"
+    },
     "node_modules/is-promise": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -1509,6 +1742,67 @@
         "node": ">=10"
       }
     },
+    "node_modules/jsdom": {
+      "version": "26.1.0",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+      "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+      "license": "MIT",
+      "dependencies": {
+        "cssstyle": "^4.2.1",
+        "data-urls": "^5.0.0",
+        "decimal.js": "^10.5.0",
+        "html-encoding-sniffer": "^4.0.0",
+        "http-proxy-agent": "^7.0.2",
+        "https-proxy-agent": "^7.0.6",
+        "is-potential-custom-element-name": "^1.0.1",
+        "nwsapi": "^2.2.16",
+        "parse5": "^7.2.1",
+        "rrweb-cssom": "^0.8.0",
+        "saxes": "^6.0.0",
+        "symbol-tree": "^3.2.4",
+        "tough-cookie": "^5.1.1",
+        "w3c-xmlserializer": "^5.0.0",
+        "webidl-conversions": "^7.0.0",
+        "whatwg-encoding": "^3.1.1",
+        "whatwg-mimetype": "^4.0.0",
+        "whatwg-url": "^14.1.1",
+        "ws": "^8.18.0",
+        "xml-name-validator": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "canvas": "^3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "canvas": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/jsdom/node_modules/agent-base": {
+      "version": "7.1.3",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/jsdom/node_modules/https-proxy-agent": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.1.2",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/kareem": {
       "version": "2.6.3",
       "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
@@ -1539,6 +1833,12 @@
         "uc.micro": "^2.0.0"
       }
     },
+    "node_modules/lru-cache": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+      "license": "ISC"
+    },
     "node_modules/make-dir": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -1934,6 +2234,12 @@
         "set-blocking": "^2.0.0"
       }
     },
+    "node_modules/nwsapi": {
+      "version": "2.2.20",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
+      "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+      "license": "MIT"
+    },
     "node_modules/object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -2009,6 +2315,30 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/parse5": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+      "license": "MIT",
+      "dependencies": {
+        "entities": "^6.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
+    },
+    "node_modules/parse5/node_modules/entities": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
+      "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
     "node_modules/parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -2186,6 +2516,12 @@
         "node": ">= 18"
       }
     },
+    "node_modules/rrweb-cssom": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+      "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+      "license": "MIT"
+    },
     "node_modules/safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -2198,6 +2534,18 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "license": "MIT"
     },
+    "node_modules/saxes": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+      "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+      "license": "ISC",
+      "dependencies": {
+        "xmlchars": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=v12.22.7"
+      }
+    },
     "node_modules/semver": {
       "version": "7.7.1",
       "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -2472,6 +2820,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/symbol-tree": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+      "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+      "license": "MIT"
+    },
     "node_modules/tar": {
       "version": "6.2.1",
       "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
@@ -2529,6 +2883,24 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "node_modules/tldts": {
+      "version": "6.1.86",
+      "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+      "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+      "license": "MIT",
+      "dependencies": {
+        "tldts-core": "^6.1.86"
+      },
+      "bin": {
+        "tldts": "bin/cli.js"
+      }
+    },
+    "node_modules/tldts-core": {
+      "version": "6.1.86",
+      "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+      "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+      "license": "MIT"
+    },
     "node_modules/toidentifier": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -2538,6 +2910,18 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/tough-cookie": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+      "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "tldts": "^6.1.32"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
     "node_modules/tr46": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
@@ -2633,6 +3017,18 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/w3c-xmlserializer": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+      "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+      "license": "MIT",
+      "dependencies": {
+        "xml-name-validator": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/webidl-conversions": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@@ -2642,6 +3038,27 @@
         "node": ">=12"
       }
     },
+    "node_modules/whatwg-encoding": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+      "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+      "license": "MIT",
+      "dependencies": {
+        "iconv-lite": "0.6.3"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/whatwg-mimetype": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+      "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/whatwg-url": {
       "version": "14.2.0",
       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
@@ -2702,6 +3119,42 @@
       "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
       "license": "ISC"
     },
+    "node_modules/ws": {
+      "version": "8.18.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
+      "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xml-name-validator": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+      "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/xmlchars": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+      "license": "MIT"
+    },
     "node_modules/xtend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -2754,4 +3207,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index f47aa6a..790d7c9 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
     "@types/ejs": "^3.1.5",
     "@types/express": "^5.0.1",
     "@types/express-session": "^1.18.1",
+    "@types/jsdom": "^21.1.7",
     "@types/markdown-it": "^14.1.2",
     "@types/node": "^22.15.3",
     "typescript": "^5.8.3"
@@ -35,14 +36,16 @@
     "cookie-session": "^2.1.0",
     "copyfiles": "^2.4.1",
     "csrf-csrf": "^4.0.0",
+    "dompurify": "^3.2.5",
     "dotenv": "^16.5.0",
     "ejs": "^3.1.10",
     "express": "^5.1.0",
     "express-mongo-sanitize": "^2.2.0",
     "express-rate-limit": "^7.5.0",
+    "jsdom": "^26.1.0",
     "markdown-it": "^14.1.0",
     "markdown-it-sub": "^2.0.0",
     "mongoose": "^8.14.1",
     "serve-favicon": "^2.5.0"
   }
-}
\ No newline at end of file
+}
diff --git a/src/server/models/Problem.ts b/src/server/models/Problem.ts
index 8741754..279257a 100644
--- a/src/server/models/Problem.ts
+++ b/src/server/models/Problem.ts
@@ -14,6 +14,7 @@ interface ProblemInterface {
   creationDateAndTime: Date;
   releaseDateAndTime: Date;
   hidden: boolean;
+  author: string;
 }
 
 interface ProblemMethods {
@@ -35,7 +36,8 @@ const problemSchema = new Schema({
   correctAnswers: Array<ProblemCorrectAnswerInterface>,
   creationDateAndTime: Date,
   releaseDateAndTime: Date,
-  hidden: Boolean
+  hidden: Boolean,
+  author: String
 });
 
 problemSchema.static(
diff --git a/src/server/routes/administrator-dashboard/add-problem.ts b/src/server/routes/administrator-dashboard/add-problem.ts
new file mode 100644
index 0000000..de35129
--- /dev/null
+++ b/src/server/routes/administrator-dashboard/add-problem.ts
@@ -0,0 +1,191 @@
+import express from "express";
+import { log } from "../../utilities/log";
+import { Problem } from "../../models/Problem";
+const router = express.Router();
+import ExpressMongoSanitize from "express-mongo-sanitize";
+import { JSDOM } from "jsdom";
+import DOMPurify from "dompurify";
+
+const window = new JSDOM("").window;
+const purify = DOMPurify(window);
+
+function authorized(request: express.Request) {
+  return request.authentication.ok && request.authentication.isAdministrator;
+}
+
+router.get(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    response.render("pages/administrator-dashboard/add-problem.ejs", {
+      authentication: request.authentication,
+      csrfToken: request.generatedCSRFToken,
+      sessionID: request.sessionID,
+      diagnosticMessage: ""
+    });
+    return;
+  }
+);
+
+router.post(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    const validationResult = await validateProblem(request);
+    if (!validationResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: validationResult.reason
+      });
+      return;
+    }
+
+    const addResult = await addProblem(request);
+    if (!addResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: addResult.reason
+      });
+      return;
+    }
+
+    log.info(
+      `User ${request.authentication.username} added new problem with ID ${request.body["problem-id"]}`
+    );
+    response.redirect(`/problem/${request.body["problem-id"]}`);
+    return;
+  }
+);
+
+async function validateProblem(request: express.Request) {
+  const fields = [
+    "problem-name",
+    "problem-statement",
+    "problem-id",
+    "correct-password",
+    "problem-number",
+    "problem-release-timestamp"
+  ];
+
+  for (const field of fields) {
+    if (request.body[field].toString().trim().length === 0) {
+      log.error(`Field empty on ${field}, unable to add problem.`);
+      return {
+        ok: false,
+        reason: `Field empty on ${field}, unable to add problem.`
+      };
+    }
+  }
+
+  const sanitizedID = ExpressMongoSanitize.sanitize(request.body["problem-id"]);
+  const existingProblem = await Problem.findOne({ problemID: sanitizedID });
+  if (existingProblem) {
+    return {
+      ok: false,
+      reason: `Problem with ID ${sanitizedID} already exists.`
+    };
+  }
+
+  if (request.body["problem-name"].length > 128) {
+    return {
+      ok: false,
+      reason: `Problem name too long.`
+    };
+  }
+
+  if (request.body["problem-statement"].length > 16000) {
+    return {
+      ok: false,
+      reason: `Problem statement too long.`
+    };
+  }
+
+  if (request.body["problem-id"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem ID too long.`
+    };
+  }
+
+  if (request.body["correct-password"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem's answer is too long.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-number"]))) {
+    return {
+      ok: false,
+      reason: `Problem number isn't a integer.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-release-timestamp"]))) {
+    return {
+      ok: false,
+      reason: `Release timestamp isn't a integer.`
+    };
+  }
+
+  return {
+    ok: true,
+    reason: `All checks passed.`
+  };
+}
+
+async function addProblem(request: express.Request) {
+  const problem = new Problem();
+  const body = request.body;
+  problem.problemName = purify.sanitize(body["problem-name"]);
+  problem.problemStatement = body["problem-statement"];
+  problem.problemID = purify.sanitize(body["problem-id"]);
+  problem.correctPassword = purify.sanitize(body["correct-password"]);
+  problem.problemNumber = parseInt(body["problem-number"]);
+  problem.correctAnswers = [];
+  problem.creationDateAndTime = new Date();
+  problem.releaseDateAndTime = new Date(
+    parseInt(body["problem-release-timestamp"])
+  );
+  problem.hidden =
+    body["problem-hidden"] === "on" || body["problem-hidden"] === true;
+  problem.author = body["problem-author"] || request.authentication.username;
+
+  try {
+    await problem.save();
+  } catch (error) {
+    log.error("Unable to add problem.");
+    if (error instanceof Error) {
+      log.error(error.stack);
+    } else {
+      log.error(error);
+    }
+    return {
+      ok: false,
+      reason: `Unable to add problem due to an internal server error. If this persists, please contact the server administrator.`
+    };
+  }
+
+  return {
+    ok: true,
+    reason: `All checks passed.`
+  };
+}
+
+export { router };
diff --git a/src/server/views/pages/administrator-dashboard/add-problem.ejs b/src/server/views/pages/administrator-dashboard/add-problem.ejs
new file mode 100644
index 0000000..18b105f
--- /dev/null
+++ b/src/server/views/pages/administrator-dashboard/add-problem.ejs
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+  <title>UntitledPasswordFindingGame</title>
+  <%- include("../../partials/head.ejs")%>
+  <link rel="stylesheet" type="text/css" href="/assets/css/global.css" />
+</head>
+
+<body>
+  <div id="container">
+    <header>
+      <%- include("../../partials/header.ejs") %>
+    </header>
+    <main>
+      <h1>Add Problem</h1>
+      <hr>
+      <form action="" method="POST">
+        <input type="text" name="problem-name" placeholder="Problem Name">
+        <br>
+        <textarea name="problem-statement" maxlength="16000" placeholder="Enter problem statement here... (Markdown, max. 16000 characters)"></textarea>
+        <br>
+        <input type="text" name="problem-id" placeholder="Problem ID">
+        <br>
+        <input type="text" name="correct-password" placeholder="Correct Password">
+        <br>
+        <input type="number" name="problem-number" placeholder="Problem Number">
+        <br>
+        <input type="number" name="problem-release-timestamp" placeholder="Timestamp to release">
+        <br>
+        <input type="text" name="problem-author" placeholder="Person to credit. Leave blank for current user.">
+        <br>
+        <div>
+          <input type="checkbox" name="problem-hidden"> Hidden Problem (must type URL directly)
+        </div>
+        <%- include("../../partials/csrf.ejs",{sessionID:sessionID,csrfToken:csrfToken}) %>
+        <% if (diagnosticMessage) { %>
+        <div class="message--error">
+          <%= diagnosticMessage %>
+        </div>
+        <% } %>
+        <input type="submit" name="Add Problem">
+      </form>
+    </main>
+    <footer>
+      <%- include("../../partials/footer.ejs") %>
+    </footer>
+  </div>
+</body>
\ No newline at end of file
diff --git a/src/server/views/pages/administrator.ejs b/src/server/views/pages/administrator.ejs
index 903d2dd..609d86b 100644
--- a/src/server/views/pages/administrator.ejs
+++ b/src/server/views/pages/administrator.ejs
@@ -16,7 +16,7 @@
     </header>
     <h1>Administrator Dashboard</h1>
     <main>
-      <form action="/administrator/new-problem">
+      <form action="/administrator/add-problem">
         <input type="submit" value="Add a New Problem">
       </form>
       <form action="/administrator/new-contest">


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/server/routes/administrator-dashboard/add-problem.ts (1)</summary><blockquote>

`30-67`: **Reduce code duplication in route handlers.**

Both route handlers share similar code for rendering the form with error messages. Consider extracting this to a helper function.

```diff
+ function renderProblemForm(
+   request: express.Request,
+   response: express.Response,
+   diagnosticMessage: string
+ ) {
+   response.render("pages/administrator-dashboard/add-problem.ejs", {
+     authentication: request.authentication,
+     csrfToken: request.generatedCSRFToken,
+     sessionID: request.sessionID,
+     diagnosticMessage
+   });
+ }

 router.post(
   "/administrator/add-problem",
   async (request: express.Request, response) => {
     if (!authorized(request)) {
       const username = request.authentication?.username ?? "";
       log.warn(`${username} tried to access admin page without permission.`);
       response.redirect("/");
       return;
     }

     const validationResult = validateProblem(request);
     if (!validationResult.ok) {
-      response.render("pages/administrator-dashboard/add-problem.ejs", {
-        authentication: request.authentication,
-        csrfToken: request.generatedCSRFToken,
-        sessionID: request.sessionID,
-        diagnosticMessage: validationResult.reason
-      });
+      renderProblemForm(request, response, validationResult.reason);
       return;
     }

     const addResult = await addProblem(request);
     if (!addResult.ok) {
-      response.render("pages/administrator-dashboard/add-problem.ejs", {
-        authentication: request.authentication,
-        csrfToken: request.generatedCSRFToken,
-        sessionID: request.sessionID,
-        diagnosticMessage: addResult.reason
-      });
+      renderProblemForm(request, response, addResult.reason);
       return;
     }

     // ...rest of handler...
   }
 );
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between efffe9577fcd02ee97fcf2d7aae7b7c48fccd4e2 and d753f5f8b84df821ed72bca20a2eef16faf7a3a0.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `src/server/models/Problem.ts` (2 hunks)
* `src/server/routes/administrator-dashboard/add-problem.ts` (1 hunks)
* `src/server/views/pages/administrator-dashboard/add-problem.ejs` (1 hunks)
* `src/server/views/pages/administrator.ejs` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>src/server/models/Problem.ts (2)</summary>

`17-17`: **Good addition of the author field to the interface.**

Adding the author field to the problem interface is a good enhancement that allows for proper attribution of problems. This aligns well with the new form field in the admin interface.

---

`39-40`: **Schema update correctly implements the author field.**

The schema properly includes the author field as a String type, matching the interface definition.

</details>
<details>
<summary>src/server/views/pages/administrator.ejs (1)</summary>

`19-19`: **Route path updated to match new implementation.**

The form action URL has been correctly changed to `/administrator/add-problem` to align with the newly created route handler.

</details>
<details>
<summary>src/server/views/pages/administrator-dashboard/add-problem.ejs (1)</summary>

`38-41`: _⚠️ Potential issue_

**Ensure diagnostic message is properly escaped to prevent XSS.**

The diagnostic message is rendered directly into the HTML without escaping, which could lead to XSS if it contains HTML or script tags.

```diff
- <div class="message--error">
-   <%= diagnosticMessage %>
- </div>
+ <div class="message--error">
+   <%- diagnosticMessage.replace(/</g, '&lt;').replace(/>/g, '&gt;') %>
+ </div>
```

Alternatively, use the EJS escaped output tag:

```diff
- <div class="message--error">
-   <%= diagnosticMessage %>
- </div>
+ <div class="message--error">
+   <%- diagnosticMessage %>
+ </div>
```



> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>src/server/routes/administrator-dashboard/add-problem.ts (1)</summary>

`6-8`: **LGTM: Authorization check is properly implemented.**

The authorization function correctly checks if the user is authenticated and has administrator privileges.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-09T09:18:42Z

Review comments:
diff_hunk: @@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+  <title>UntitledPasswordFindingGame</title>
+  <%- include("../../partials/head.ejs")%>
+  <link rel="stylesheet" type="text/css" href="/assets/css/global.css" />
+</head>
+
+<body>
+  <div id="container">
+    <header>
+      <%- include("../../partials/header.ejs") %>
+    </header>
+    <main>
+      <h1>Add Problem</h1>
+      <hr>
+      <form action="" method="POST">
+        <input type="text" name="problem-name" placeholder="Problem Name">
+        <br>
+        <textarea name="problem-statement" maxlength="16000" placeholder="Enter problem statement here... (Markdown, max. 16000 characters)"></textarea>
+        <br>
+        <input type="text" name="problem-id" placeholder="Problem ID">
+        <br>
+        <input type="text" name="correct-password" placeholder="Correct Password">
+        <br>
+        <input type="number" name="problem-number" placeholder="Problem Number">
+        <br>
+        <input type="number" name="problem-release-timestamp" placeholder="Timestamp to release">
+        <br>
+        <input type="text" name="problem-author" placeholder="Person to credit. Leave blank for current user.">
+        <br>
+        <div>
+          <input type="checkbox" name="problem-hidden"> Hidden Problem (must type URL directly)
+        </div>
+        <%- include("../../partials/csrf.ejs",{sessionID:sessionID,csrfToken:csrfToken}) %>
+        <% if (diagnosticMessage) { %>
+        <div class="message--error">
+          <%= diagnosticMessage %>
+        </div>
+        <% } %>
+        <input type="submit" name="Add Problem">
+      </form>
path: src/server/views/pages/administrator-dashboard/add-problem.ejs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider improving form accessibility and validation.**

While the form functions correctly, there are several improvements that could be made:

1. Add `<label>` elements for each form field instead of relying solely on placeholders
2. Include client-side validation attributes (required, minlength, maxlength, pattern)
3. Fix the submit button (it has `name="Add Problem"` which will be submitted as form data)


```diff
- <form action="" method="POST">
-   <input type="text" name="problem-name" placeholder="Problem Name">
+ <form action="" method="POST">
+   <label for="problem-name">Problem Name:</label>
+   <input type="text" id="problem-name" name="problem-name" placeholder="Problem Name" required maxlength="128">

- <br>
- <textarea name="problem-statement" maxlength="16000" placeholder="Enter problem statement here... (Markdown, max. 16000 characters)"></textarea>
+ <br>
+ <label for="problem-statement">Problem Statement:</label>
+ <textarea id="problem-statement" name="problem-statement" maxlength="16000" placeholder="Enter problem statement here... (Markdown, max. 16000 characters)" required></textarea>

- <br>
- <input type="text" name="problem-id" placeholder="Problem ID">
+ <br>
+ <label for="problem-id">Problem ID:</label>
+ <input type="text" id="problem-id" name="problem-id" placeholder="Problem ID" required maxlength="64">

- <br>
- <input type="text" name="correct-password" placeholder="Correct Password">
+ <br>
+ <label for="correct-password">Correct Password:</label>
+ <input type="text" id="correct-password" name="correct-password" placeholder="Correct Password" required maxlength="64">

- <br>
- <input type="number" name="problem-number" placeholder="Problem Number">
+ <br>
+ <label for="problem-number">Problem Number:</label>
+ <input type="number" id="problem-number" name="problem-number" placeholder="Problem Number" required>

- <br>
- <input type="number" name="problem-release-timestamp" placeholder="Timestamp to release">
+ <br>
+ <label for="problem-release-timestamp">Release Timestamp:</label>
+ <input type="number" id="problem-release-timestamp" name="problem-release-timestamp" placeholder="Timestamp to release" required>

- <br>
- <input type="text" name="problem-author" placeholder="Person to credit. Leave blank for current user.">
+ <br>
+ <label for="problem-author">Author:</label>
+ <input type="text" id="problem-author" name="problem-author" placeholder="Person to credit. Leave blank for current user." maxlength="128">

- <br>
- <div>
-   <input type="checkbox" name="problem-hidden"> Hidden Problem (must type URL directly)
- </div>
+ <br>
+ <div>
+   <input type="checkbox" id="problem-hidden" name="problem-hidden">
+   <label for="problem-hidden">Hidden Problem (must type URL directly)</label>
+ </div>

- <input type="submit" name="Add Problem">
+ <input type="submit" value="Add Problem">
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      <form action="" method="POST">
        <label for="problem-name">Problem Name:</label>
        <input type="text" id="problem-name" name="problem-name" placeholder="Problem Name" required maxlength="128">
        <br>
        <label for="problem-statement">Problem Statement:</label>
        <textarea id="problem-statement" name="problem-statement" maxlength="16000" placeholder="Enter problem statement here... (Markdown, max. 16000 characters)" required></textarea>
        <br>
        <label for="problem-id">Problem ID:</label>
        <input type="text" id="problem-id" name="problem-id" placeholder="Problem ID" required maxlength="64">
        <br>
        <label for="correct-password">Correct Password:</label>
        <input type="text" id="correct-password" name="correct-password" placeholder="Correct Password" required maxlength="64">
        <br>
        <label for="problem-number">Problem Number:</label>
        <input type="number" id="problem-number" name="problem-number" placeholder="Problem Number" required>
        <br>
        <label for="problem-release-timestamp">Release Timestamp:</label>
        <input type="number" id="problem-release-timestamp" name="problem-release-timestamp" placeholder="Timestamp to release" required>
        <br>
        <label for="problem-author">Author:</label>
        <input type="text" id="problem-author" name="problem-author" placeholder="Person to credit. Leave blank for current user." maxlength="128">
        <br>
        <div>
          <input type="checkbox" id="problem-hidden" name="problem-hidden">
          <label for="problem-hidden">Hidden Problem (must type URL directly)</label>
        </div>
        <%- include("../../partials/csrf.ejs",{sessionID:sessionID,csrfToken:csrfToken}) %>
        <% if (diagnosticMessage) { %>
        <div class="message--error">
          <%= diagnosticMessage %>
        </div>
        <% } %>
        <input type="submit" value="Add Problem">
      </form>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:18:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+import express from "express";
+import { log } from "../../utilities/log";
+import { Problem } from "../../models/Problem";
+const router = express.Router();
+
+function authorized(request: express.Request) {
+  return request.authentication.ok && request.authentication.isAdministrator;
+}
+
+router.get(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    response.render("pages/administrator-dashboard/add-problem.ejs", {
+      authentication: request.authentication,
+      csrfToken: request.generatedCSRFToken,
+      sessionID: request.sessionID,
+      diagnosticMessage: ""
+    });
+    return;
+  }
+);
+
+router.post(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    const validationResult = validateProblem(request);
+    if (!validationResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: validationResult.reason
+      });
+      return;
+    }
+
+    const addResult = await addProblem(request);
+    if (!addResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: addResult.reason
+      });
+      return;
+    }
+
+    log.info(
+      `User ${request.authentication.username} added new problem with ID ${request.body["problem-id"]}`
+    );
+    response.redirect(`/problem/${request.body["problem-id"]}`);
+    return;
+  }
+);
+
+function validateProblem(request: express.Request) {
+  const fields = [
+    "problem-name",
+    "problem-statement",
+    "problem-id",
+    "correct-password",
+    "problem-number",
+    "problem-release-timestamp"
+  ];
+
+  for (const field of fields) {
+    if (request.body[field].toString().trim().length === 0) {
+      log.error(`Field empty on ${field}, unable to add problem.`);
+      return {
+        ok: false,
+        reason: `Field empty on ${field}, unable to add problem.`
+      };
+    }
+  }
+
+  if (request.body["problem-name"].length > 128) {
+    return {
+      ok: false,
+      reason: `Problem name too long.`
+    };
+  }
+
+  if (request.body["problem-statement"].length > 16000) {
+    return {
+      ok: false,
+      reason: `Problem statement too long.`
+    };
+  }
+
+  if (request.body["problem-id"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem ID too long.`
+    };
+  }
+
+  if (request.body["correct-password"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem's answer is too long.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-number"]))) {
+    return {
+      ok: false,
+      reason: `Problem number isn't a integer.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-release-timestamp"]))) {
+    return {
+      ok: false,
+      reason: `Release timestamp isn't a integer.`
+    };
+  }
+
+  return {
+    ok: true,
+    reason: `All checks passed.`
+  };
+}
+
+async function addProblem(request: express.Request) {
+  const problem = new Problem();
+  const body = request.body;
+  problem.problemName = body["problem-name"];
+  problem.problemStatement = body["problem-statement"];
+  problem.problemID = body["problem-id"];
+  problem.correctPassword = body["correct-password"];
+  problem.problemNumber = parseInt(body["problem-number"]);
+  problem.correctAnswers = [];
+  problem.creationDateAndTime = new Date();
+  problem.releaseDateAndTime = new Date(
+    parseInt(body["problem-release-timestamp"])
+  );
+  problem.hidden = body["problem-hidden"];
+  problem.author = body["problem-author"] || request.authentication.username;
path: src/server/routes/administrator-dashboard/add-problem.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Ensure proper boolean handling for the hidden checkbox.**

The checkbox handling needs improvement, as `body["problem-hidden"]` will be undefined if not checked, not false.

```diff
-  problem.hidden = body["problem-hidden"];
+  problem.hidden = body["problem-hidden"] === "on" || body["problem-hidden"] === true;
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  problem.hidden = body["problem-hidden"] === "on" || body["problem-hidden"] === true;
  problem.author = body["problem-author"] || request.authentication.username;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:18:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+import express from "express";
+import { log } from "../../utilities/log";
+import { Problem } from "../../models/Problem";
+const router = express.Router();
+
+function authorized(request: express.Request) {
+  return request.authentication.ok && request.authentication.isAdministrator;
+}
+
+router.get(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    response.render("pages/administrator-dashboard/add-problem.ejs", {
+      authentication: request.authentication,
+      csrfToken: request.generatedCSRFToken,
+      sessionID: request.sessionID,
+      diagnosticMessage: ""
+    });
+    return;
+  }
+);
+
+router.post(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    const validationResult = validateProblem(request);
+    if (!validationResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: validationResult.reason
+      });
+      return;
+    }
+
+    const addResult = await addProblem(request);
+    if (!addResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: addResult.reason
+      });
+      return;
+    }
+
+    log.info(
+      `User ${request.authentication.username} added new problem with ID ${request.body["problem-id"]}`
+    );
+    response.redirect(`/problem/${request.body["problem-id"]}`);
+    return;
+  }
+);
+
+function validateProblem(request: express.Request) {
+  const fields = [
+    "problem-name",
+    "problem-statement",
+    "problem-id",
+    "correct-password",
+    "problem-number",
+    "problem-release-timestamp"
+  ];
+
+  for (const field of fields) {
+    if (request.body[field].toString().trim().length === 0) {
+      log.error(`Field empty on ${field}, unable to add problem.`);
+      return {
+        ok: false,
+        reason: `Field empty on ${field}, unable to add problem.`
+      };
+    }
+  }
+
+  if (request.body["problem-name"].length > 128) {
+    return {
+      ok: false,
+      reason: `Problem name too long.`
+    };
+  }
+
+  if (request.body["problem-statement"].length > 16000) {
+    return {
+      ok: false,
+      reason: `Problem statement too long.`
+    };
+  }
+
+  if (request.body["problem-id"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem ID too long.`
+    };
+  }
+
+  if (request.body["correct-password"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem's answer is too long.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-number"]))) {
+    return {
+      ok: false,
+      reason: `Problem number isn't a integer.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-release-timestamp"]))) {
+    return {
+      ok: false,
+      reason: `Release timestamp isn't a integer.`
+    };
+  }
+
+  return {
+    ok: true,
+    reason: `All checks passed.`
+  };
+}
path: src/server/routes/administrator-dashboard/add-problem.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add validation for duplicate problem IDs.**

The validation function checks for empty fields and length constraints, but it doesn't check if a problem with the same ID already exists. This could lead to database conflicts or overwriting existing problems.

```diff
- function validateProblem(request: express.Request) {
+ async function validateProblem(request: express.Request) {
   // ... existing validation ...

+  // Check for duplicate problem ID
+  const existingProblem = await Problem.findOne({ problemID: request.body["problem-id"] });
+  if (existingProblem) {
+    return {
+      ok: false,
+      reason: `Problem with ID ${request.body["problem-id"]} already exists.`
+    };
+  }

   return {
     ok: true,
     reason: `All checks passed.`
   };
 }
```

Note: This would also require changing the function call on line 40 to `await validateProblem(request)`.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:18:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+import express from "express";
+import { log } from "../../utilities/log";
+import { Problem } from "../../models/Problem";
+const router = express.Router();
+
+function authorized(request: express.Request) {
+  return request.authentication.ok && request.authentication.isAdministrator;
+}
+
+router.get(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    response.render("pages/administrator-dashboard/add-problem.ejs", {
+      authentication: request.authentication,
+      csrfToken: request.generatedCSRFToken,
+      sessionID: request.sessionID,
+      diagnosticMessage: ""
+    });
+    return;
+  }
+);
+
+router.post(
+  "/administrator/add-problem",
+  async (request: express.Request, response) => {
+    if (!authorized(request)) {
+      const username = request.authentication?.username ?? "";
+      log.warn(`${username} tried to access admin page without permission.`);
+      response.redirect("/");
+      return;
+    }
+
+    const validationResult = validateProblem(request);
+    if (!validationResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: validationResult.reason
+      });
+      return;
+    }
+
+    const addResult = await addProblem(request);
+    if (!addResult.ok) {
+      response.render("pages/administrator-dashboard/add-problem.ejs", {
+        authentication: request.authentication,
+        csrfToken: request.generatedCSRFToken,
+        sessionID: request.sessionID,
+        diagnosticMessage: addResult.reason
+      });
+      return;
+    }
+
+    log.info(
+      `User ${request.authentication.username} added new problem with ID ${request.body["problem-id"]}`
+    );
+    response.redirect(`/problem/${request.body["problem-id"]}`);
+    return;
+  }
+);
+
+function validateProblem(request: express.Request) {
+  const fields = [
+    "problem-name",
+    "problem-statement",
+    "problem-id",
+    "correct-password",
+    "problem-number",
+    "problem-release-timestamp"
+  ];
+
+  for (const field of fields) {
+    if (request.body[field].toString().trim().length === 0) {
+      log.error(`Field empty on ${field}, unable to add problem.`);
+      return {
+        ok: false,
+        reason: `Field empty on ${field}, unable to add problem.`
+      };
+    }
+  }
+
+  if (request.body["problem-name"].length > 128) {
+    return {
+      ok: false,
+      reason: `Problem name too long.`
+    };
+  }
+
+  if (request.body["problem-statement"].length > 16000) {
+    return {
+      ok: false,
+      reason: `Problem statement too long.`
+    };
+  }
+
+  if (request.body["problem-id"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem ID too long.`
+    };
+  }
+
+  if (request.body["correct-password"].length > 64) {
+    return {
+      ok: false,
+      reason: `Problem's answer is too long.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-number"]))) {
+    return {
+      ok: false,
+      reason: `Problem number isn't a integer.`
+    };
+  }
+
+  if (isNaN(parseInt(request.body["problem-release-timestamp"]))) {
+    return {
+      ok: false,
+      reason: `Release timestamp isn't a integer.`
+    };
+  }
+
+  return {
+    ok: true,
+    reason: `All checks passed.`
+  };
+}
+
+async function addProblem(request: express.Request) {
+  const problem = new Problem();
+  const body = request.body;
+  problem.problemName = body["problem-name"];
+  problem.problemStatement = body["problem-statement"];
+  problem.problemID = body["problem-id"];
+  problem.correctPassword = body["correct-password"];
+  problem.problemNumber = parseInt(body["problem-number"]);
+  problem.correctAnswers = [];
+  problem.creationDateAndTime = new Date();
+  problem.releaseDateAndTime = new Date(
+    parseInt(body["problem-release-timestamp"])
+  );
+  problem.hidden = body["problem-hidden"];
+  problem.author = body["problem-author"] || request.authentication.username;
+
+  try {
+    await problem.save();
+  } catch (error) {
+    log.error("Unable to add problem.");
+    if (error instanceof Error) {
+      log.error(error.stack);
+    } else {
+      log.error(error);
+    }
+    return {
+      ok: false,
+      reason: `Unable to add problem due to an internal server error. If this persists, please contact the server administrator.`
+    };
+  }
+
+  return {
+    ok: true,
+    reason: `All checks passed.`
+  };
+}
path: src/server/routes/administrator-dashboard/add-problem.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Sanitize user input before saving to database.**

The function directly assigns user input to the problem object without sanitizing it, which could potentially lead to security issues.

```diff
 async function addProblem(request: express.Request) {
   const problem = new Problem();
   const body = request.body;
-  problem.problemName = body["problem-name"];
-  problem.problemStatement = body["problem-statement"];
-  problem.problemID = body["problem-id"];
-  problem.correctPassword = body["correct-password"];
+  problem.problemName = sanitizeInput(body["problem-name"]);
+  problem.problemStatement = body["problem-statement"]; // Consider allowing Markdown but sanitizing HTML
+  problem.problemID = sanitizeInput(body["problem-id"]);
+  problem.correctPassword = sanitizeInput(body["correct-password"]);
   problem.problemNumber = parseInt(body["problem-number"]);
   problem.correctAnswers = [];
   problem.creationDateAndTime = new Date();
   problem.releaseDateAndTime = new Date(
     parseInt(body["problem-release-timestamp"])
   );
   problem.hidden = body["problem-hidden"];
-  problem.author = body["problem-author"] || request.authentication.username;
+  problem.author = sanitizeInput(body["problem-author"] || request.authentication.username);

   // ... rest of function ...
 }

+ // Add this utility function
+ function sanitizeInput(input: string): string {
+   if (!input) return input;
+   // Basic sanitization to prevent XSS
+   return input
+     .replace(/</g, '&lt;')
+     .replace(/>/g, '&gt;')
+     .trim();
+ }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
async function addProblem(request: express.Request) {
  const problem = new Problem();
  const body = request.body;
-  problem.problemName = body["problem-name"];
-  problem.problemStatement = body["problem-statement"];
-  problem.problemID = body["problem-id"];
-  problem.correctPassword = body["correct-password"];
+  problem.problemName = sanitizeInput(body["problem-name"]);
+  problem.problemStatement = body["problem-statement"]; // Consider allowing Markdown but sanitizing HTML
+  problem.problemID = sanitizeInput(body["problem-id"]);
+  problem.correctPassword = sanitizeInput(body["correct-password"]);
   problem.problemNumber = parseInt(body["problem-number"]);
   problem.correctAnswers = [];
   problem.creationDateAndTime = new Date();
   problem.releaseDateAndTime = new Date(
     parseInt(body["problem-release-timestamp"])
   );
   problem.hidden = body["problem-hidden"];
-  problem.author = body["problem-author"] || request.authentication.username;
+  problem.author = sanitizeInput(body["problem-author"] || request.authentication.username);

  try {
    await problem.save();
  } catch (error) {
    log.error("Unable to add problem.");
    if (error instanceof Error) {
      log.error(error.stack);
    } else {
      log.error(error);
    }
    return {
      ok: false,
      reason: `Unable to add problem due to an internal server error. If this persists, please contact the server administrator.`
    };
  }

  return {
    ok: true,
    reason: `All checks passed.`
  };
}

// Utility function to sanitize input
function sanitizeInput(input: string): string {
  if (!input) return input;
  // Basic sanitization to prevent XSS
  return input
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .trim();
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:18:41Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-09T09:15:27Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on this repository.
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

A new feature for adding problems via the administrator dashboard was implemented. This includes a new Express route and form page for problem creation, updates to the problem model to support an `author` field, and a change to the dashboard to use the new route. The new form supports validation, CSRF protection, and error handling.

## Changes

| File(s)                                                                                     | Change Summary                                                                                                          |
|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| `src/server/models/Problem.ts`                                                              | Added `author: string` to `ProblemInterface` and included `author` in the Mongoose schema.                             |
| `src/server/routes/administrator-dashboard/add-problem.ts`                                  | Introduced a new Express router for GET/POST `/administrator/add-problem`, including validation and DB insertion logic.|
| `src/server/views/pages/administrator-dashboard/add-problem.ejs`                            | Added a new EJS template for the "Add Problem" form, supporting all required fields and error display.                 |
| `src/server/views/pages/administrator.ejs`                                                  | Updated form action URL from `/administrator/new-problem` to `/administrator/add-problem`.                             |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Admin as Administrator
    participant Browser as Browser
    participant Server as Express Server
    participant DB as Database

    Admin->>Browser: Navigates to /administrator/add-problem
    Browser->>Server: GET /administrator/add-problem
    Server->>Browser: Render add-problem form (with CSRF token)

    Admin->>Browser: Fills and submits form
    Browser->>Server: POST /administrator/add-problem (form data)
    Server->>Server: Validate input
    alt Validation fails
        Server->>Browser: Re-render form with error message
    else Validation succeeds
        Server->>DB: Save new Problem (including author)
        alt DB error
            Server->>Browser: Re-render form with DB error
        else Success
            Server->>Browser: Redirect to new problem page
        end
    end
```

## Poem

> A hop and a skip, a new form appears,  
> For admins to add problems, with cheers!  
> Now with an author, each riddle’s complete,  
> Validation and CSRF keep it neat.  
> With paws on the keyboard, I code with delight—  
> New problems are blooming, oh what a sight!  
> 🥕✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=mistertfy64/UntitledPasswordFindingGame&utm_content=16):

> ‼️ **IMPORTANT**
> Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: mistertfy64
created_at: 2025-05-09T09:15:34Z
body: @coderabbitai review
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-05-09T09:15:40Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: NONE