\ No newline at end of file
diff --git a/documentation/index.md b/documentation/index.md
index fafa3b8..4e5348d 100644
--- a/documentation/index.md
+++ b/documentation/index.md
@@ -1,7 +1,7 @@
# Links
-- [Create User](./create_user.md)
-- [Get User Tests](./get_user_tests.md)
-- [Leaderboard](./leaderboard.md)
-- [Login](./login.md)
-- [Post Test](./create_test.md)
\ No newline at end of file
+- [POST `/api/Create_User`](./create_user.md)
+- [POST `/api/Post_Test`](./create_test.md)
+- [GET `/api/Get_User_Tests`](./get_user_tests.md)
+- [GET `/api/Leaderboard`](./leaderboard.md)
+- [GET `/api/Login`](./login.md)
diff --git a/documentation/leaderboard.html b/documentation/leaderboard.html
index 71de656..e2c4045 100644
--- a/documentation/leaderboard.html
+++ b/documentation/leaderboard.html
@@ -1,3 +1,4 @@
+
@@ -19,29 +20,48 @@
}
}
-
Leaderboard
-
This API endpoint retrieves the highest test data from each user and returns it as a JSON array.
-
Endpoint
-
GET /api/leaderboard
-
-
Request Parameters
-
This endpoint does not require any request parameters.
-
Example Request
-
curl -X GET "https://example.com/api/leaderboard"
-
Response
+
Leaderboard API Endpoint
+
GET /api/leaderboard
+
+
Returns the highest test data from each user as a JSON array. The data includes metrics such as username, words per minute (WPM), accuracy percentage, the time taken for the test, and the length of the test for a comprehensive overview of user performance.
+
Responses
+
+
+ 200 OK: Successfully retrieves the leaderboard data.
+
+ 404 Not Found: Indicates that the leaderboard was not found.
+
+ 500 Internal Server Error: Indicates an issue with accessing the database.
+ wpm: Words per minute, indicating the typing speed.
+
+ accuracy: The accuracy of the user's typing, in percentage.
+
+ test_time: The total time taken to complete the test, in seconds.
+
+ test_length: The length of the test, typically measured in number of words.
+
\ No newline at end of file
diff --git a/documentation/leaderboard.md b/documentation/leaderboard.md
index 0ebc29b..a4d14ae 100644
--- a/documentation/leaderboard.md
+++ b/documentation/leaderboard.md
@@ -1,38 +1,40 @@
-# Leaderboard
+# Leaderboard API Endpoint
-This API endpoint retrieves the highest test data from each user and returns it as a JSON array.
+## GET `/api/leaderboard`
-## Endpoint
+Returns the highest test data from each user as a JSON array. The data includes metrics such as username, words per minute (WPM), accuracy percentage, the time taken for the test, and the length of the test for a comprehensive overview of user performance.
-```
-GET /api/leaderboard
-```
+## Responses
-## Request Parameters
+- `200 OK`: Successfully retrieves the leaderboard data.
+- `404 Not Found`: Indicates that the leaderboard was not found.
+- `500 Internal Server Error`: Indicates an issue with accessing the database.
-This endpoint does not require any request parameters.
-
-## Example Request
-
-```bash
-curl -X GET "https://example.com/api/leaderboard"
-```
-
-## Response
+## Example Response
```json
[
- {
- "userName": "user_1",
- "wpm": 85,
- },
- {
- "userName": "user_2",
- "score": 80,
- },
- {
- "userName": "user_3",
- "wpm": 73,
- }
+ {
+ "username": "user1",
+ "wpm": 75,
+ "accuracy": 97,
+ "test_time": 120,
+ "test_length": 250
+ },
+ {
+ "username": "user2",
+ "wpm": 73,
+ "accuracy": 95,
+ "test_time": 115,
+ "test_length": 240
+ }
]
-```
\ No newline at end of file
+```
+
+## Fields
+
+- `username`: The name of the user.
+- `wpm`: Words per minute, indicating the typing speed.
+- `accuracy`: The accuracy of the user's typing, in percentage.
+- `test_time`: The total time taken to complete the test, in seconds.
+- `test_length`: The length of the test, typically measured in number of words.
\ No newline at end of file
diff --git a/public/leaderboard/index.html b/public/leaderboard/index.html
new file mode 100644
index 0000000..14e6cb3
--- /dev/null
+++ b/public/leaderboard/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ Leaderboard
+
+
+
+
Leaderboard
+
+
+
+
+
Username
+
WPM
+
Accuracy (%)
+
Test Time (s)
+
Test Length (characters)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/leaderboard/script.js b/public/leaderboard/script.js
new file mode 100644
index 0000000..0b78b2c
--- /dev/null
+++ b/public/leaderboard/script.js
@@ -0,0 +1,22 @@
+async function fetchLeaderboardData() {
+ const response = await fetch('/api/leaderboard');
+ if (!response.ok) {
+ console.error('Failed to fetch leaderboard data');
+ return;
+ }
+ const data = await response.json();
+ const tableBody = document.getElementById('leaderboardTable').getElementsByTagName('tbody')[0];
+ tableBody.innerHTML = ''; // Clear existing rows
+ data.forEach(item => {
+ const row = tableBody.insertRow();
+ row.insertCell(0).innerText = item.username;
+ row.insertCell(1).innerText = item.wpm;
+ row.insertCell(2).innerText = item.accuracy;
+ row.insertCell(3).innerText = item.test_time;
+ row.insertCell(4).innerText = item.test_length;
+ });
+}
+
+// Ensure this function is called on page load and when the refresh button is clicked.
+document.addEventListener('DOMContentLoaded', fetchLeaderboardData);
+document.getElementById('refreshButton').addEventListener('click', fetchLeaderboardData);
diff --git a/public/leaderboard/styles.css b/public/leaderboard/styles.css
new file mode 100644
index 0000000..361e575
--- /dev/null
+++ b/public/leaderboard/styles.css
@@ -0,0 +1,56 @@
+body {
+ font-family: Arial, sans-serif;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 20px;
+}
+table {
+ width: 60%;
+ border-collapse: collapse;
+ margin-top: 20px;
+}
+th, td {
+ border: 1px solid #ddd;
+ text-align: left;
+ padding: 8px;
+}
+th {
+ background-color: #f2f2f2;
+}
+tr:nth-child(even) {
+ background-color: #f9f9f9;
+}
+.refresh-button {
+ cursor: pointer;
+ border: none;
+ background-color: transparent;
+ display: flex;
+ align-items: center;
+ color: #4CAF50;
+}
+
+.refresh-button:hover {
+ transform: rotate(90deg);
+}
+
+/* Dark Mode styles */
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #121212;
+ color: #e0e0e0;
+ }
+ table {
+ border-color: #424242;
+ }
+ th {
+ background-color: #333;
+ }
+ tr:nth-child(even) {
+ background-color: #2a2a2a;
+ }
+ .refresh-icon {
+ fill: #90caf9;
+ }
+}
diff --git a/src/api/sql.rs b/src/api/sql.rs
index c264175..e1edf3a 100644
--- a/src/api/sql.rs
+++ b/src/api/sql.rs
@@ -183,7 +183,7 @@ impl Database {
_user_id: u32,
) -> Result, sqlx::Error> {
let tests = sqlx::query!(
- "SELECT users.username, tests.wpm
+ "SELECT users.username, tests.wpm, tests.accuracy, tests.test_time, tests.test_length
FROM tests
INNER JOIN users ON users.user_id = tests.user_id
GROUP BY users.username
@@ -197,6 +197,9 @@ impl Database {
.map(|test| LeaderBoardTest {
username: test.username.clone(),
wpm: test.wpm.unwrap() as u8,
+ accuracy: test.accuracy.unwrap() as u8,
+ test_time: test.test_time.unwrap() as u32,
+ test_length: test.test_length.unwrap() as u32
})
.collect();
@@ -262,11 +265,21 @@ pub struct Test {
accuracy: u8,
}
-/// struct that represents all the data that gets sent to the user
-/// when they make a leaderboard request
+/// Represents leaderboard data sent to the user upon request.
+/// This data includes username, words per minute (WPM), accuracy percentage,
+/// the time taken for the test, and the length of the test.
+///
+/// - `username`: The name of the user.
+/// - `wpm`: Words per minute, indicating the typing speed of the user.
+/// - `accuracy`: The accuracy of the user's typing, in percentage.
+/// - `test_time`: The total time taken to complete the test, in seconds.
+/// - `test_length`: The length of the test, typically measured in number of words.
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct LeaderBoardTest {
username: String,
wpm: u8,
+ accuracy: u8,
+ test_time: u32,
+ test_length: u32,
}
diff --git a/src/cors.rs b/src/cors.rs
new file mode 100644
index 0000000..cffec3f
--- /dev/null
+++ b/src/cors.rs
@@ -0,0 +1,25 @@
+use rocket::fairing::{Fairing, Info, Kind};
+use rocket::http::Header;
+use rocket::{Request, Response};
+
+pub struct CORS;
+
+#[rocket::async_trait]
+impl Fairing for CORS {
+ fn info(&self) -> Info {
+ Info {
+ name: "Add CORS headers to responses",
+ kind: Kind::Response,
+ }
+ }
+
+ async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
+ response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
+ response.set_header(Header::new(
+ "Access-Control-Allow-Methods",
+ "GET"
+ ));
+ response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
+ response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
+ }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index be6e8aa..193e35b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,38 +6,16 @@
//! - move structures into a different file
//! - find a way to make logging in more secure (password hashes?)
-// use rocket::fairing::{Fairing, Info, Kind};
-// use rocket::http::Header;
-// use rocket::{Request, Response};
-
-// pub struct CORS;
-
-// #[rocket::async_trait]
-// impl Fairing for CORS {
-// fn info(&self) -> Info {
-// Info {
-// name: "Add CORS headers to responses",
-// kind: Kind::Response,
-// }
-// }
-
-// async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
-// response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
-// response.set_header(Header::new(
-// "Access-Control-Allow-Methods",
-// "POST, GET, PATCH, OPTIONS",
-// ));
-// response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
-// response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
-// }
-// }
+mod cors;
+use crate::cors::CORS;
// Imports for rocket
#[macro_use]
extern crate rocket;
use rocket::{
- fs::{relative, FileServer},
- Build, Rocket,
+ fs::{relative, FileServer},
+ response::Redirect,
+ Build, Rocket
};
mod api;
@@ -61,34 +39,43 @@ fn test() -> &'static str {
"Hello World! I'm A rocket Webserver"
}
+#[get("/")]
+async fn typing_redirect() -> Redirect {
+ Redirect::to(uri!("/typing/index.html"))
+}
+
/// The main function which builds and launches the
/// webserver with all appropriate routes and fileservers
#[launch]
async fn rocket() -> Rocket {
rocket::build()
- // .attach(CORS)
- // testing only, should return "Hello world"
- .mount("/test", routes![test])
+ // Allow external to any get API methods
+ .attach(CORS)
+
// hosts the api routes necessary for the website
// to interact with the database
.mount("/api/documentation", FileServer::from(relative!("documentation")))
.register("/api/documentation", catchers![documentation_not_found])
- .mount(
- "/api",
- routes![
- sign_up,
- create_test,
- login,
- get_tests,
- leaderboard,
- new_test,
- ],
- )
+ .mount("/api",routes![
+ sign_up,
+ login,
+
+ new_test,
+ create_test,
+ get_tests,
+ leaderboard
+ ])
.register("/api", catchers![api_not_found])
// hosts the fileserver
+ .mount("/typing", routes![typing_redirect])
.mount("/typing", FileServer::from(relative!("public")))
.register("/typing", catchers![frontend_not_found])
+ // The state which allows routes to access the database
.manage(Database::new().await.unwrap())
+
+
+ // testing only, should return "Hello world"
+ .mount("/test", routes![test])
}