Changed to separate page structure & api updates #1
@ -41,7 +41,7 @@ This project is a web server built using the Rocket framework in Rust. It provid
|
|||||||
## Dependencies
|
## Dependencies
|
||||||
- **Rocket**: Web framework for Rust.
|
- **Rocket**: Web framework for Rust.
|
||||||
- **Serde**: Serialization and deserialization library for Rust.
|
- **Serde**: Serialization and deserialization library for Rust.
|
||||||
- **Rusqlite**: SQLite database driver for Rust.
|
- **SQLX**: SQLite database driver for Rust.
|
||||||
- **Rand**: Random number generation library for Rust.
|
- **Rand**: Random number generation library for Rust.
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="github-markdown.css">
|
|
||||||
</head>
|
|
||||||
<body class="markdown-body">
|
|
||||||
<style>
|
|
||||||
.markdown-body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 980px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<p>Post Test Data This API endpoint allows you to post test data, recording the results of a test taken by a user.</p>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Endpoint</h1><a id="user-content-endpoint" class="anchor" aria-label="Permalink: Endpoint" href="#endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<pre><code>POST /api/post_test
|
|
||||||
</code></pre>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Request Parameters</h1><a id="user-content-request-parameters" class="anchor" aria-label="Permalink: Request Parameters" href="#request-parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Parameter</th>
|
|
||||||
<th align="center">Type</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>testType</td>
|
|
||||||
<td align="center">String</td>
|
|
||||||
<td>Type of the test (e.g., "typing", "multiple choice")</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>testLength</td>
|
|
||||||
<td align="center">Integer</td>
|
|
||||||
<td>Length of the test in number of items or questions</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>testTime</td>
|
|
||||||
<td align="center">Integer</td>
|
|
||||||
<td>Duration of the test in seconds</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>testSeed</td>
|
|
||||||
<td align="center">String</td>
|
|
||||||
<td>Seed for generating randomized test content</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>quoteId</td>
|
|
||||||
<td align="center">String</td>
|
|
||||||
<td>Identifier for a specific quote, if applicable</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>wpm</td>
|
|
||||||
<td align="center">Integer</td>
|
|
||||||
<td>Words per minute (typing speed)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>accuracy</td>
|
|
||||||
<td align="center">Integer</td>
|
|
||||||
<td>Accuracy of responses (e.g., percentage)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>userId</td>
|
|
||||||
<td align="center">String</td>
|
|
||||||
<td>Identifier of the user taking the test</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>secret</td>
|
|
||||||
<td align="center">String</td>
|
|
||||||
<td>Secret key for authentication and authorization</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Example Request</h1><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-shell"><pre>curl -X POST <span class="pl-s"><span class="pl-pds">"</span>https://example.com/api/post_test<span class="pl-pds">"</span></span> \
|
|
||||||
-H <span class="pl-s"><span class="pl-pds">"</span>Content-Type: application/json<span class="pl-pds">"</span></span> <span class="pl-cce">\ </span>
|
|
||||||
-d <span class="pl-s"><span class="pl-pds">'</span>{ </span>
|
|
||||||
<span class="pl-s"> "testType": "typing", </span>
|
|
||||||
<span class="pl-s"> "testLength": 100, </span>
|
|
||||||
<span class="pl-s"> "testTime": 600, </span>
|
|
||||||
<span class="pl-s"> "testSeed": "random_seed_123", </span>
|
|
||||||
<span class="pl-s"> "quoteId": "quote_456", </span>
|
|
||||||
<span class="pl-s"> "wpm": 65.5, </span>
|
|
||||||
<span class="pl-s"> "accuracy": 98.2, </span>
|
|
||||||
<span class="pl-s"> "userId": "user_789", </span>
|
|
||||||
<span class="pl-s"> "secret": "your_secret_key_here" </span>
|
|
||||||
<span class="pl-s"> }<span class="pl-pds">'</span></span></pre></div>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Example Response</h1><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<p>Upon successful submission, you will receive a JSON response with the following structure:</p>
|
|
||||||
<div class="highlight highlight-source-json"><pre>{
|
|
||||||
<span class="pl-ent">"status"</span>: <span class="pl-s"><span class="pl-pds">"</span>success<span class="pl-pds">"</span></span>,
|
|
||||||
<span class="pl-ent">"message"</span>: <span class="pl-s"><span class="pl-pds">"</span>Test results successfully recorded<span class="pl-pds">"</span></span>,
|
|
||||||
<span class="pl-ent">"testId"</span>: <span class="pl-s"><span class="pl-pds">"</span>test_123456789<span class="pl-pds">"</span></span>
|
|
||||||
}</pre></div>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<code>status</code>: Indicates the status of the request (either "success" or "error").</li>
|
|
||||||
<li>
|
|
||||||
<code>message</code>: Describes the outcome of the request.</li>
|
|
||||||
<li>
|
|
||||||
<code>testId</code>: Unique identifier assigned to the recorded test data.</li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,54 +0,0 @@
|
|||||||
Post Test Data This API endpoint allows you to post test data, recording the results of a test taken by a user.
|
|
||||||
|
|
||||||
# Endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/post_test
|
|
||||||
```
|
|
||||||
|
|
||||||
# Request Parameters
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| ---------- | :-----: | ---------------------------------------------------- |
|
|
||||||
| testType | String | Type of the test (e.g., "typing", "multiple choice") |
|
|
||||||
| testLength | Integer | Length of the test in number of items or questions |
|
|
||||||
| testTime | Integer | Duration of the test in seconds |
|
|
||||||
| testSeed | String | Seed for generating randomized test content |
|
|
||||||
| quoteId | String | Identifier for a specific quote, if applicable |
|
|
||||||
| wpm | Integer | Words per minute (typing speed) |
|
|
||||||
| accuracy | Integer | Accuracy of responses (e.g., percentage) |
|
|
||||||
| userId | String | Identifier of the user taking the test |
|
|
||||||
| secret | String | Secret key for authentication and authorization |
|
|
||||||
|
|
||||||
# Example Request
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST "https://example.com/api/post_test" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"testType": "typing",
|
|
||||||
"testLength": 100,
|
|
||||||
"testTime": 600,
|
|
||||||
"testSeed": "random_seed_123",
|
|
||||||
"quoteId": "quote_456",
|
|
||||||
"wpm": 65.5,
|
|
||||||
"accuracy": 98.2,
|
|
||||||
"userId": "user_789",
|
|
||||||
"secret": "your_secret_key_here"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
# Example Response
|
|
||||||
|
|
||||||
Upon successful submission, you will receive a JSON response with the following structure:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "success",
|
|
||||||
"message": "Test results successfully recorded",
|
|
||||||
"testId": "test_123456789"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `status`: Indicates the status of the request (either "success" or "error").
|
|
||||||
- `message`: Describes the outcome of the request.
|
|
||||||
- `testId`: Unique identifier assigned to the recorded test data.
|
|
@ -1,36 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="github-markdown.css">
|
|
||||||
</head>
|
|
||||||
<body class="markdown-body">
|
|
||||||
<style>
|
|
||||||
.markdown-body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 980px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Create User</h1><a id="user-content-create-user" class="anchor" aria-label="Permalink: Create User" href="#create-user"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<p>Creates a new user in the database.
|
|
||||||
Endpoint</p>
|
|
||||||
<div class="highlight highlight-source-js"><pre><span class="pl-c1">POST</span> <span class="pl-c1">/</span><span class="pl-s1">api</span><span class="pl-c1">/</span><span class="pl-s1">create_user</span></pre></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Request Body</h2><a id="user-content-request-body" class="anchor" aria-label="Permalink: Request Body" href="#request-body"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-json"><pre>{
|
|
||||||
<span class="pl-ent">"username"</span>: <span class="pl-s"><span class="pl-pds">"</span>example_user<span class="pl-pds">"</span></span>,
|
|
||||||
<span class="pl-ent">"password"</span>: <span class="pl-s"><span class="pl-pds">"</span>example_password<span class="pl-pds">"</span></span>
|
|
||||||
<br>}</pre></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-shell"><pre>curl -X POST <span class="pl-s"><span class="pl-pds">"</span>https://example.com/api/create_user<span class="pl-pds">"</span></span> \
|
|
||||||
-H <span class="pl-s"><span class="pl-pds">"</span>Content-Type: application/json<span class="pl-pds">"</span></span> \
|
|
||||||
-d <span class="pl-s"><span class="pl-pds">'</span>{"username": "example_user", "password": "example_password"}<span class="pl-pds">'</span></span></pre></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -4,12 +4,27 @@
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
## Request Parameters
|
||||||
|
|
||||||
|
- `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.
|
||||||
|
|
||||||
## Responses
|
## Responses
|
||||||
|
|
||||||
- `200 OK`: Successfully retrieves the leaderboard data.
|
- `200 OK`: Successfully retrieves the leaderboard data.
|
||||||
- `404 Not Found`: Indicates that the leaderboard was not found.
|
- `404 Not Found`: Indicates that the leaderboard was not found.
|
||||||
- `500 Internal Server Error`: Indicates an issue with accessing the database.
|
- `500 Internal Server Error`: Indicates an issue with accessing the database.
|
||||||
|
|
||||||
|
|
||||||
|
## Example Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X GET "https://url/api/leaderboard"
|
||||||
|
```
|
||||||
|
|
||||||
## Example Response
|
## Example Response
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@ -31,10 +46,3 @@ Returns the highest test data from each user as a JSON array. The data includes
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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.
|
|
21
documentation/get_ping.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Ping API Endpoint
|
||||||
|
|
||||||
|
## GET `/api/ping`
|
||||||
|
|
||||||
|
Shows whether the api is up and running. Accessible from http://url/api/ping
|
||||||
|
|
||||||
|
## Responses
|
||||||
|
|
||||||
|
- `200 OK`: The api is up and running
|
||||||
|
|
||||||
|
## Example Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X GET "https://url/api/ping
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Response
|
||||||
|
|
||||||
|
```
|
||||||
|
"Hello World! I'm A rocket Webserver"
|
||||||
|
```
|
39
documentation/get_test.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Test API Endpoint
|
||||||
|
|
||||||
|
## GET `/api/test`
|
||||||
|
|
||||||
|
Returns 100 random words to be used to give a typing test. Retrieved from `wordlist.txt`
|
||||||
|
|
||||||
|
## Responses
|
||||||
|
|
||||||
|
- `200 OK`: Successfully retrieves the 100 words.
|
||||||
|
- `404 Not Found`: Indicates that the wordlist was not found.
|
||||||
|
|
||||||
|
## Example Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X GET "https://url/api/test"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
"separate",
|
||||||
|
"stand",
|
||||||
|
"think",
|
||||||
|
"island",
|
||||||
|
"have",
|
||||||
|
"air",
|
||||||
|
"heard",
|
||||||
|
"notice",
|
||||||
|
"yellow",
|
||||||
|
"smell",
|
||||||
|
"heart",
|
||||||
|
"island",
|
||||||
|
"chief",
|
||||||
|
"view",
|
||||||
|
"top",
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
31
documentation/get_user_login.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# User Login Endpoint
|
||||||
|
|
||||||
|
## GET `/api/user/login/<username>/<password>`
|
||||||
|
|
||||||
|
Takes the user's login information and returns the user's user ID, which can be used to identify their tests, etc. Accessible from http://url/api/login.
|
||||||
|
|
||||||
|
## Request Parameters
|
||||||
|
|
||||||
|
- `username`: The username of the user.
|
||||||
|
- `password`: The password of the user.
|
||||||
|
|
||||||
|
## Responses
|
||||||
|
|
||||||
|
- `200 OK`: Successfully retrieves the user ID.
|
||||||
|
- `401 Unauthorized`: Indicates that the login credentials are invalid.
|
||||||
|
- `500 Internal Server Error`: Indicates an issue with accessing the database.
|
||||||
|
|
||||||
|
## Example Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X GET "https://url/api/user/login/example_user/example_password"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id": "1234567890",
|
||||||
|
"secret": "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
}
|
||||||
|
```
|
30
documentation/get_user_test.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Get User Test
|
||||||
|
|
||||||
|
## GET `/api/user/test/<user_id>/<secret>`
|
||||||
|
|
||||||
|
Retrieves tests associated with a specific user from the database.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `user_id` - User ID of the user whose tests need to be retrieved
|
||||||
|
- `secret` - Secret key for authentication
|
||||||
|
|
||||||
|
## Example Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X GET "https://url/api/user/test/123/your_secret_key_here"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"test_type": "words",
|
||||||
|
"test_length": 100,
|
||||||
|
"test_time": 300,
|
||||||
|
"test_seed": 987654321,
|
||||||
|
"quote_id": 123,
|
||||||
|
"wpm": 65,
|
||||||
|
"accuracy": 98
|
||||||
|
}
|
||||||
|
```
|
@ -1,61 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="github-markdown.css">
|
|
||||||
</head>
|
|
||||||
<body class="markdown-body">
|
|
||||||
<style>
|
|
||||||
.markdown-body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 980px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Get User Tests</h1><a id="user-content-get-user-tests" class="anchor" aria-label="Permalink: Get User Tests" href="#get-user-tests"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<p>Retrieves tests associated with a specific user from the database.</p>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Endpoint</h2><a id="user-content-endpoint" class="anchor" aria-label="Permalink: Endpoint" href="#endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-js"><pre><span class="pl-c1">GET</span> <span class="pl-c1">/</span><span class="pl-s1">api</span><span class="pl-c1">/</span><span class="pl-s1">get_tests</span><span class="pl-c1">/</span><span class="pl-c1"><</span><span class="pl-ent">user_id</span><span class="pl-c1">></span>/<span class="pl-c1"><</span><span class="pl-ent">secret</span><span class="pl-c1">></span></pre></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Parameters</h2><a id="user-content-parameters" class="anchor" aria-label="Permalink: Parameters" href="#parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Parameter</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>user_id</td>
|
|
||||||
<td>integer</td>
|
|
||||||
<td>User ID of the user whose tests need to be retrieved</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>secret</td>
|
|
||||||
<td>String</td>
|
|
||||||
<td>Secret key for authentication</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://example.com/api/get_tests/123/your_secret_key_here<span class="pl-pds">"</span></span></pre></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-json"><pre>{
|
|
||||||
<span class="pl-ent">"test_type"</span>: <span class="pl-s"><span class="pl-pds">"</span>words<span class="pl-pds">"</span></span>,
|
|
||||||
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">100</span>,
|
|
||||||
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">300</span>,
|
|
||||||
<span class="pl-ent">"test_seed"</span>: <span class="pl-c1">987654321</span>,
|
|
||||||
<span class="pl-ent">"quote_id"</span>: <span class="pl-c1">123</span>,
|
|
||||||
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">65</span>,
|
|
||||||
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">98</span>
|
|
||||||
<br>}</pre></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,36 +1,42 @@
|
|||||||
# Get User Tests
|
# Get User Tests
|
||||||
|
|
||||||
|
## GET `/api/user/tests/<user_id>/<secret>`
|
||||||
|
|
||||||
Retrieves tests associated with a specific user from the database.
|
Retrieves tests associated with a specific user from the database.
|
||||||
|
|
||||||
## Endpoint
|
## Request Parameters
|
||||||
|
|
||||||
```js
|
- `user_id` - User ID of the user whose tests need to be retrieved
|
||||||
GET /api/get_tests/<user_id>/<secret>
|
- `secret` - Secret key for authentication
|
||||||
```
|
|
||||||
|
|
||||||
## Parameters
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| - | - | - |
|
|
||||||
| user_id | integer | User ID of the user whose tests need to be retrieved |
|
|
||||||
| secret | String | Secret key for authentication |
|
|
||||||
|
|
||||||
## Example Request
|
## Example Request
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X GET "https://example.com/api/get_tests/123/your_secret_key_here"
|
curl -X GET "https://url/api/user/tests/123/your_secret_key_here"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example Response
|
## Example Response
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"test_type": "words",
|
"test_type": "words",
|
||||||
"test_length": 100,
|
"test_length": 300,
|
||||||
"test_time": 300,
|
"test_time": 60,
|
||||||
"test_seed": 987654321,
|
"test_seed": 0,
|
||||||
"quote_id": 123,
|
"quote_id": 0,
|
||||||
"wpm": 65,
|
"wpm": 60,
|
||||||
|
"accuracy": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"test_type": "words",
|
||||||
|
"test_length": 47,
|
||||||
|
"test_time": 15,
|
||||||
|
"test_seed": 0,
|
||||||
|
"quote_id": 0,
|
||||||
|
"wpm": 37,
|
||||||
"accuracy": 98
|
"accuracy": 98
|
||||||
}
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
```
|
```
|
70
documentation/html/get_leaderboard.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Leaderboard API Endpoint</h1><a id="user-content-leaderboard-api-endpoint" class="anchor" aria-label="Permalink: Leaderboard API Endpoint" href="#leaderboard-api-endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">GET <code>/api/leaderboard</code>
|
||||||
|
</h2><a id="user-content-get-apileaderboard" class="anchor" aria-label="Permalink: GET /api/leaderboard" href="#get-apileaderboard"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>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.</p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Request Parameters</h2><a id="user-content-request-parameters" class="anchor" aria-label="Permalink: Request Parameters" href="#request-parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>username</code>: The name of the user.</li>
|
||||||
|
<li>
|
||||||
|
<code>wpm</code>: Words per minute, indicating the typing speed.</li>
|
||||||
|
<li>
|
||||||
|
<code>accuracy</code>: The accuracy of the user's typing, in percentage.</li>
|
||||||
|
<li>
|
||||||
|
<code>test_time</code>: The total time taken to complete the test, in seconds.</li>
|
||||||
|
<li>
|
||||||
|
<code>test_length</code>: The length of the test, typically measured in number of words.</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Responses</h2><a id="user-content-responses" class="anchor" aria-label="Permalink: Responses" href="#responses"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>200 OK</code>: Successfully retrieves the leaderboard data.</li>
|
||||||
|
<li>
|
||||||
|
<code>404 Not Found</code>: Indicates that the leaderboard was not found.</li>
|
||||||
|
<li>
|
||||||
|
<code>500 Internal Server Error</code>: Indicates an issue with accessing the database.</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://url/api/leaderboard<span class="pl-pds">"</span></span></pre></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-json"><pre>[
|
||||||
|
{
|
||||||
|
<span class="pl-ent">"username"</span>: <span class="pl-s"><span class="pl-pds">"</span>user1<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">75</span>,
|
||||||
|
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">97</span>,
|
||||||
|
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">120</span>,
|
||||||
|
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">250</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
<span class="pl-ent">"username"</span>: <span class="pl-s"><span class="pl-pds">"</span>user2<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">73</span>,
|
||||||
|
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">95</span>,
|
||||||
|
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">115</span>,
|
||||||
|
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">240</span>
|
||||||
|
}
|
||||||
|
]</pre></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
39
documentation/html/get_ping.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Ping API Endpoint</h1><a id="user-content-ping-api-endpoint" class="anchor" aria-label="Permalink: Ping API Endpoint" href="#ping-api-endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">GET <code>/api/ping</code>
|
||||||
|
</h2><a id="user-content-get-apiping" class="anchor" aria-label="Permalink: GET /api/ping" href="#get-apiping"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>Shows whether the api is up and running. Accessible from <a href="http://url/api/ping" rel="nofollow">http://url/api/ping</a></p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Responses</h2><a id="user-content-responses" class="anchor" aria-label="Permalink: Responses" href="#responses"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>200 OK</code>: The api is up and running</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://url/api/ping</span></pre></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<pre><code>"Hello World! I'm A rocket Webserver"
|
||||||
|
</code></pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
57
documentation/html/get_test.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Test API Endpoint</h1><a id="user-content-test-api-endpoint" class="anchor" aria-label="Permalink: Test API Endpoint" href="#test-api-endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">GET <code>/api/test</code>
|
||||||
|
</h2><a id="user-content-get-apitest" class="anchor" aria-label="Permalink: GET /api/test" href="#get-apitest"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>Returns 100 random words to be used to give a typing test. Retrieved from <code>wordlist.txt</code></p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Responses</h2><a id="user-content-responses" class="anchor" aria-label="Permalink: Responses" href="#responses"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>200 OK</code>: Successfully retrieves the 100 words.</li>
|
||||||
|
<li>
|
||||||
|
<code>404 Not Found</code>: Indicates that the wordlist was not found.</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://url/api/test<span class="pl-pds">"</span></span></pre></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-json"><pre>[
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>separate<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>stand<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>think<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>island<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>have<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>air<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>heard<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>notice<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>yellow<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>smell<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>heart<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>island<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>chief<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>view<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-s"><span class="pl-pds">"</span>top<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ii">...</span>
|
||||||
|
]</pre></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
52
documentation/html/get_user_login.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">User Login Endpoint</h1><a id="user-content-user-login-endpoint" class="anchor" aria-label="Permalink: User Login Endpoint" href="#user-login-endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">GET <code>/api/user/login/<username>/<password></code>
|
||||||
|
</h2><a id="user-content-get-apiuserloginusernamepassword" class="anchor" aria-label="Permalink: GET /api/user/login/<username>/<password>" href="#get-apiuserloginusernamepassword"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>Takes the user's login information and returns the user's user ID, which can be used to identify their tests, etc. Accessible from <a href="http://url/api/login" rel="nofollow">http://url/api/login</a>.</p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Request Parameters</h2><a id="user-content-request-parameters" class="anchor" aria-label="Permalink: Request Parameters" href="#request-parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>username</code>: The username of the user.</li>
|
||||||
|
<li>
|
||||||
|
<code>password</code>: The password of the user.</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Responses</h2><a id="user-content-responses" class="anchor" aria-label="Permalink: Responses" href="#responses"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>200 OK</code>: Successfully retrieves the user ID.</li>
|
||||||
|
<li>
|
||||||
|
<code>401 Unauthorized</code>: Indicates that the login credentials are invalid.</li>
|
||||||
|
<li>
|
||||||
|
<code>500 Internal Server Error</code>: Indicates an issue with accessing the database.</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://url/api/user/login/example_user/example_password<span class="pl-pds">"</span></span></pre></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-json"><pre>{
|
||||||
|
<span class="pl-ent">"user_id"</span>: <span class="pl-s"><span class="pl-pds">"</span>1234567890<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ent">"secret"</span>: <span class="pl-s"><span class="pl-pds">"</span>abcdefghijklmnopqrstuvwxyz<span class="pl-pds">"</span></span>
|
||||||
|
}</pre></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
47
documentation/html/get_user_test.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Get User Test</h1><a id="user-content-get-user-test" class="anchor" aria-label="Permalink: Get User Test" href="#get-user-test"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element"><code>GET /api/user/test/<user_id>/<secret></code></h2><a id="user-content-get-apiusertestuser_idsecret" class="anchor" aria-label="Permalink: GET /api/user/test/<user_id>/<secret>" href="#get-apiusertestuser_idsecret"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>Retrieves tests associated with a specific user from the database.</p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Parameters</h2><a id="user-content-parameters" class="anchor" aria-label="Permalink: Parameters" href="#parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>user_id</code> - User ID of the user whose tests need to be retrieved</li>
|
||||||
|
<li>
|
||||||
|
<code>secret</code> - Secret key for authentication</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://url/api/user/test/123/your_secret_key_here<span class="pl-pds">"</span></span></pre></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-json"><pre>{
|
||||||
|
<span class="pl-ent">"test_type"</span>: <span class="pl-s"><span class="pl-pds">"</span>words<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">100</span>,
|
||||||
|
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">300</span>,
|
||||||
|
<span class="pl-ent">"test_seed"</span>: <span class="pl-c1">987654321</span>,
|
||||||
|
<span class="pl-ent">"quote_id"</span>: <span class="pl-c1">123</span>,
|
||||||
|
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">65</span>,
|
||||||
|
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">98</span>
|
||||||
|
}</pre></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
60
documentation/html/get_user_tests.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Get User Tests</h1><a id="user-content-get-user-tests" class="anchor" aria-label="Permalink: Get User Tests" href="#get-user-tests"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">GET <code>/api/user/tests/<user_id>/<secret></code>
|
||||||
|
</h2><a id="user-content-get-apiusertestsuser_idsecret" class="anchor" aria-label="Permalink: GET /api/user/tests/<user_id>/<secret>" href="#get-apiusertestsuser_idsecret"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>Retrieves tests associated with a specific user from the database.</p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Request Parameters</h2><a id="user-content-request-parameters" class="anchor" aria-label="Permalink: Request Parameters" href="#request-parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>user_id</code> - User ID of the user whose tests need to be retrieved</li>
|
||||||
|
<li>
|
||||||
|
<code>secret</code> - Secret key for authentication</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://url/api/user/tests/123/your_secret_key_here<span class="pl-pds">"</span></span></pre></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-json"><pre>[
|
||||||
|
{
|
||||||
|
<span class="pl-ent">"test_type"</span>: <span class="pl-s"><span class="pl-pds">"</span>words<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">300</span>,
|
||||||
|
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">60</span>,
|
||||||
|
<span class="pl-ent">"test_seed"</span>: <span class="pl-c1">0</span>,
|
||||||
|
<span class="pl-ent">"quote_id"</span>: <span class="pl-c1">0</span>,
|
||||||
|
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">60</span>,
|
||||||
|
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">100</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
<span class="pl-ent">"test_type"</span>: <span class="pl-s"><span class="pl-pds">"</span>words<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">47</span>,
|
||||||
|
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">15</span>,
|
||||||
|
<span class="pl-ent">"test_seed"</span>: <span class="pl-c1">0</span>,
|
||||||
|
<span class="pl-ent">"quote_id"</span>: <span class="pl-c1">0</span>,
|
||||||
|
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">37</span>,
|
||||||
|
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">98</span>
|
||||||
|
},
|
||||||
|
<span class="pl-ii">...</span>
|
||||||
|
]</pre></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
39
documentation/html/index.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Routes</h1><a id="user-content-routes" class="anchor" aria-label="Permalink: Routes" href="#routes"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element"><code>/api</code></h2><a id="user-content-api" class="anchor" aria-label="Permalink: /api" href="#api"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h3 class="heading-element"><code>/user</code></h3><a id="user-content-user" class="anchor" aria-label="Permalink: /user" href="#user"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li><a href="./post_user_create.html">POST <code>/create</code></a></li>
|
||||||
|
<li><a href="./get_user_login.html">GET <code>/login</code></a></li>
|
||||||
|
<li><a href="./get_user_tests.html">GET <code>/tests</code></a></li>
|
||||||
|
<li><a href="./post_user_test.html">POST <code>/test</code></a></li>
|
||||||
|
<li><a href="./get_user_test.html">GET <code>/test</code></a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h3 class="heading-element"><a href="./get_ping.html">GET <code>/ping</code></a></h3><a id="user-content-get-ping" class="anchor" aria-label="Permalink: GET /ping" href="#get-ping"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h3 class="heading-element"><a href="./get_leaderboard.html">GET <code>/leaderboard</code></a></h3><a id="user-content-get-leaderboard" class="anchor" aria-label="Permalink: GET /leaderboard" href="#get-leaderboard"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h3 class="heading-element"><a href="./get_test.html">GET <code>/test</code></a></h3><a id="user-content-get-test" class="anchor" aria-label="Permalink: GET /test" href="#get-test"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h3 class="heading-element"><code>/Documentation</code></h3><a id="user-content-documentation" class="anchor" aria-label="Permalink: /Documentation" href="#documentation"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
38
documentation/html/post_user_create.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Create User API Endpoint</h1><a id="user-content-create-user-api-endpoint" class="anchor" aria-label="Permalink: Create User API Endpoint" href="#create-user-api-endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">POST <code>/api/user/create</code>
|
||||||
|
</h2><a id="user-content-post-apiusercreate" class="anchor" aria-label="Permalink: POST /api/user/create" href="#post-apiusercreate"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>Creates a new user in the database.</p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Request Parameters</h2><a id="user-content-request-parameters" class="anchor" aria-label="Permalink: Request Parameters" href="#request-parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-json"><pre>{
|
||||||
|
<span class="pl-ent">"username"</span>: <span class="pl-s"><span class="pl-pds">"</span>example_user<span class="pl-pds">"</span></span>,
|
||||||
|
<span class="pl-ent">"password"</span>: <span class="pl-s"><span class="pl-pds">"</span>example_password<span class="pl-pds">"</span></span>
|
||||||
|
}</pre></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X POST <span class="pl-s"><span class="pl-pds">"</span>https://url/api/user/create<span class="pl-pds">"</span></span> \
|
||||||
|
-H <span class="pl-s"><span class="pl-pds">"</span>Content-Type: application/json<span class="pl-pds">"</span></span> \
|
||||||
|
-d <span class="pl-s"><span class="pl-pds">'</span>{"username": "example_user", "password": "example_password"}<span class="pl-pds">'</span></span></pre></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
73
documentation/html/post_user_test.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="markdown-heading"><h1 class="heading-element">Create Test API Endpoint</h1><a id="user-content-create-test-api-endpoint" class="anchor" aria-label="Permalink: Create Test API Endpoint" href="#create-test-api-endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">POST <code>/api/post_test</code>
|
||||||
|
</h2><a id="user-content-post-apipost_test" class="anchor" aria-label="Permalink: POST /api/post_test" href="#post-apipost_test"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<p>Post Test Data This API endpoint allows you to post test data, recording the results of a test taken by a user.</p>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Request Parameters</h2><a id="user-content-request-parameters" class="anchor" aria-label="Permalink: Request Parameters" href="#request-parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>testType</code> - Type of the test ("words", "time", "quote", etc)</li>
|
||||||
|
<li>
|
||||||
|
<code>testLength</code> - Length of the test in number of items or</li>
|
||||||
|
<li>
|
||||||
|
<code>testTime</code> - Duration of the test in seconds</li>
|
||||||
|
<li>
|
||||||
|
<code>testSeed</code> - Seed for generating randomized test content</li>
|
||||||
|
<li>
|
||||||
|
<code>quoteId</code> - Identifier for a specific quote, if applicable</li>
|
||||||
|
<li>
|
||||||
|
<code>wpm</code> - Words per minute (typing speed)</li>
|
||||||
|
<li>
|
||||||
|
<code>accuracy</code> - Accuracy of responses (e.g., percentage)</li>
|
||||||
|
<li>
|
||||||
|
<code>userId</code> - Identifier of the user taking the test</li>
|
||||||
|
<li>
|
||||||
|
<code>secret</code> - Secret key for authentication and authorization</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Responses</h2><a id="user-content-responses" class="anchor" aria-label="Permalink: Responses" href="#responses"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>200 OK</code> - The test has been added to the database sucessfully</li>
|
||||||
|
<li>
|
||||||
|
<code>401 UNAUTHORIZED</code> - The user has not been authenticated correctly</li>
|
||||||
|
<li>
|
||||||
|
<code>500 INTERNAL SERVER ERROR</code> - There has been a database error when attempting to</li>
|
||||||
|
</ul>
|
||||||
|
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
||||||
|
<div class="highlight highlight-source-shell"><pre>curl -X POST <span class="pl-s"><span class="pl-pds">"</span>https://example.com/api/post_test<span class="pl-pds">"</span></span> \
|
||||||
|
-H <span class="pl-s"><span class="pl-pds">"</span>Content-Type: application/json<span class="pl-pds">"</span></span> <span class="pl-cce">\ </span>
|
||||||
|
-d <span class="pl-s"><span class="pl-pds">'</span>{ </span>
|
||||||
|
<span class="pl-s"> "testType": "typing", </span>
|
||||||
|
<span class="pl-s"> "testLength": 100, </span>
|
||||||
|
<span class="pl-s"> "testTime": 600, </span>
|
||||||
|
<span class="pl-s"> "testSeed": "random_seed_123", </span>
|
||||||
|
<span class="pl-s"> "quoteId": "quote_456", </span>
|
||||||
|
<span class="pl-s"> "wpm": 65.5, </span>
|
||||||
|
<span class="pl-s"> "accuracy": 98.2, </span>
|
||||||
|
<span class="pl-s"> "userId": "user_789", </span>
|
||||||
|
<span class="pl-s"> "secret": "your_secret_key_here" </span>
|
||||||
|
<span class="pl-s"> }<span class="pl-pds">'</span></span></pre></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="github-markdown.css">
|
|
||||||
</head>
|
|
||||||
<body class="markdown-body">
|
|
||||||
<style>
|
|
||||||
.markdown-body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 980px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Links</h1><a id="user-content-links" class="anchor" aria-label="Permalink: Links" href="#links"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<ul>
|
|
||||||
<li><a href="./create_user.html">POST <code>/api/Create_User</code></a></li>
|
|
||||||
<li><a href="./create_test.html">POST <code>/api/Post Test</code></a></li>
|
|
||||||
<li><a href="./get_user_tests.html">GET <code>/api/Get_User_Tests</code></a></li>
|
|
||||||
<li><a href="./leaderboard.html">GET <code>/api/Leaderboard</code></a></li>
|
|
||||||
<li><a href="./login.html">GET <code>/api/Login</code></a></li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,7 +1,13 @@
|
|||||||
# Links
|
# Routes
|
||||||
|
|
||||||
- [POST `/api/Create_User`](./create_user.md)
|
## `/api`
|
||||||
- [POST `/api/Post_Test`](./create_test.md)
|
### `/user`
|
||||||
- [GET `/api/Get_User_Tests`](./get_user_tests.md)
|
- [POST `/create`](./post_user_create.md)
|
||||||
- [GET `/api/Leaderboard`](./leaderboard.md)
|
- [GET `/login`](./get_user_login.md)
|
||||||
- [GET `/api/Login`](./login.md)
|
- [GET `/tests`](./get_user_tests.md)
|
||||||
|
- [POST `/test`](./post_user_test.md)
|
||||||
|
- [GET `/test`](./get_user_test.md)
|
||||||
|
### [GET `/ping`](./get_ping.md)
|
||||||
|
### [GET `/leaderboard`](./get_leaderboard.md)
|
||||||
|
### [GET `/test`](./get_test.md)
|
||||||
|
### `/Documentation`
|
@ -1,67 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="github-markdown.css">
|
|
||||||
</head>
|
|
||||||
<body class="markdown-body">
|
|
||||||
<style>
|
|
||||||
.markdown-body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 980px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Leaderboard API Endpoint</h1><a id="user-content-leaderboard-api-endpoint" class="anchor" aria-label="Permalink: Leaderboard API Endpoint" href="#leaderboard-api-endpoint"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">GET <code>/api/leaderboard</code>
|
|
||||||
</h2><a id="user-content-get-apileaderboard" class="anchor" aria-label="Permalink: GET /api/leaderboard" href="#get-apileaderboard"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<p>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.</p>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Responses</h2><a id="user-content-responses" class="anchor" aria-label="Permalink: Responses" href="#responses"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<code>200 OK</code>: Successfully retrieves the leaderboard data.</li>
|
|
||||||
<li>
|
|
||||||
<code>404 Not Found</code>: Indicates that the leaderboard was not found.</li>
|
|
||||||
<li>
|
|
||||||
<code>500 Internal Server Error</code>: Indicates an issue with accessing the database.</li>
|
|
||||||
</ul>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-json"><pre>[
|
|
||||||
{
|
|
||||||
<span class="pl-ent">"username"</span>: <span class="pl-s"><span class="pl-pds">"</span>user1<span class="pl-pds">"</span></span>,
|
|
||||||
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">75</span>,
|
|
||||||
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">97</span>,
|
|
||||||
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">120</span>,
|
|
||||||
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">250</span>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
<span class="pl-ent">"username"</span>: <span class="pl-s"><span class="pl-pds">"</span>user2<span class="pl-pds">"</span></span>,
|
|
||||||
<span class="pl-ent">"wpm"</span>: <span class="pl-c1">73</span>,
|
|
||||||
<span class="pl-ent">"accuracy"</span>: <span class="pl-c1">95</span>,
|
|
||||||
<span class="pl-ent">"test_time"</span>: <span class="pl-c1">115</span>,
|
|
||||||
<span class="pl-ent">"test_length"</span>: <span class="pl-c1">240</span>
|
|
||||||
}
|
|
||||||
]</pre></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Fields</h2><a id="user-content-fields" class="anchor" aria-label="Permalink: Fields" href="#fields"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<code>username</code>: The name of the user.</li>
|
|
||||||
<li>
|
|
||||||
<code>wpm</code>: Words per minute, indicating the typing speed.</li>
|
|
||||||
<li>
|
|
||||||
<code>accuracy</code>: The accuracy of the user's typing, in percentage.</li>
|
|
||||||
<li>
|
|
||||||
<code>test_time</code>: The total time taken to complete the test, in seconds.</li>
|
|
||||||
<li>
|
|
||||||
<code>test_length</code>: The length of the test, typically measured in number of words.</li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,56 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="github-markdown.css">
|
|
||||||
</head>
|
|
||||||
<body class="markdown-body">
|
|
||||||
<style>
|
|
||||||
.markdown-body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 980px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="markdown-heading"><h1 class="heading-element">Login</h1><a id="user-content-login" class="anchor" aria-label="Permalink: Login" href="#login"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<p>Authenticates a user and returns their user ID along with a secret key.
|
|
||||||
Endpoint</p>
|
|
||||||
<div class="highlight highlight-source-js"><pre><span class="pl-c1">GET</span> <span class="pl-c1">/</span><span class="pl-s1">api</span><span class="pl-c1">/</span><span class="pl-s1">login</span><span class="pl-c1">/</span><span class="pl-c1"><</span><span class="pl-ent">username</span><span class="pl-c1">></span>/<span class="pl-c1"><</span><span class="pl-ent">password</span><span class="pl-c1">></span></pre></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Parameters</h2><a id="user-content-parameters" class="anchor" aria-label="Permalink: Parameters" href="#parameters"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Parameter</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>username</td>
|
|
||||||
<td>String</td>
|
|
||||||
<td>Username of the user</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>password</td>
|
|
||||||
<td>String</td>
|
|
||||||
<td>Password of the user</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Example Request</h2><a id="user-content-example-request" class="anchor" aria-label="Permalink: Example Request" href="#example-request"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-shell"><pre>curl -X GET <span class="pl-s"><span class="pl-pds">"</span>https://example.com/api/login/example_user/example_password<span class="pl-pds">"</span></span></pre></div>
|
|
||||||
<div class="markdown-heading"><h2 class="heading-element">Example Response</h2><a id="user-content-example-response" class="anchor" aria-label="Permalink: Example Response" href="#example-response"><span aria-hidden="true" class="octicon octicon-link"></span></a></div>
|
|
||||||
<div class="highlight highlight-source-json"><pre>{
|
|
||||||
<span class="pl-ent">"user_id"</span>: <span class="pl-c1">123</span>,
|
|
||||||
<span class="pl-ent">"secret"</span>: <span class="pl-s"><span class="pl-pds">"</span>random_secret_key<span class="pl-pds">"</span></span>
|
|
||||||
}</pre></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,31 +0,0 @@
|
|||||||
# Login
|
|
||||||
|
|
||||||
Authenticates a user and returns their user ID along with a secret key.
|
|
||||||
Endpoint
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
GET /api/login/<username>/<password>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Parameters
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| - | - | - |
|
|
||||||
| username | String |Username of the user |
|
|
||||||
| password | String |Password of the user |
|
|
||||||
|
|
||||||
## Example Request
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X GET "https://example.com/api/login/example_user/example_password"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example Response
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"user_id": 123,
|
|
||||||
"secret": "random_secret_key"
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,7 +1,39 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# HTML structure
|
||||||
|
html_start='
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="github-markdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<style>
|
||||||
|
.markdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.markdown-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
'
|
||||||
|
|
||||||
|
html_end='
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'
|
||||||
|
|
||||||
|
# Iterate over each markdown file in the current directory
|
||||||
|
for file in *.md; do
|
||||||
# Read markdown content from file
|
# Read markdown content from file
|
||||||
markdown_content=$(cat $1)
|
markdown_content=$(cat "$file")
|
||||||
|
|
||||||
# Convert markdown to HTML
|
# Convert markdown to HTML
|
||||||
html_content=$(gh api \
|
html_content=$(gh api \
|
||||||
@ -12,7 +44,9 @@ html_content=$(gh api \
|
|||||||
-f text="$markdown_content"
|
-f text="$markdown_content"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write HTML content to file
|
# Write HTML content to a new file
|
||||||
echo "$html_content" > $2
|
html_file="./html/${file%.md}.html"
|
||||||
|
echo "$html_start$html_content$html_end" > "$html_file"
|
||||||
|
|
||||||
echo "Conversion completed. HTML content written to $2"
|
echo "Conversion completed. HTML content written to $html_file"
|
||||||
|
done
|
@ -1,13 +1,10 @@
|
|||||||
# Create User
|
# Create User API Endpoint
|
||||||
|
|
||||||
|
## POST `/api/user/create`
|
||||||
|
|
||||||
Creates a new user in the database.
|
Creates a new user in the database.
|
||||||
Endpoint
|
|
||||||
|
|
||||||
```js
|
## Request Parameters
|
||||||
POST /api/create_user
|
|
||||||
```
|
|
||||||
|
|
||||||
## Request Body
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -19,7 +16,7 @@ POST /api/create_user
|
|||||||
## Example Request
|
## Example Request
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST "https://example.com/api/create_user" \
|
curl -X POST "https://url/api/user/create" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"username": "example_user", "password": "example_password"}'
|
-d '{"username": "example_user", "password": "example_password"}'
|
||||||
```
|
```
|
42
documentation/post_user_test.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Create Test API Endpoint
|
||||||
|
|
||||||
|
## POST `/api/post_test`
|
||||||
|
|
||||||
|
Post Test Data This API endpoint allows you to post test data, recording the results of a test taken by a user.
|
||||||
|
|
||||||
|
## Request Parameters
|
||||||
|
|
||||||
|
- `testType` - Type of the test ("words", "time", "quote", etc)
|
||||||
|
- `testLength` - Length of the test in number of items or
|
||||||
|
- `testTime` - Duration of the test in seconds
|
||||||
|
- `testSeed` - Seed for generating randomized test content
|
||||||
|
- `quoteId` - Identifier for a specific quote, if applicable
|
||||||
|
- `wpm` - Words per minute (typing speed)
|
||||||
|
- `accuracy` - Accuracy of responses (e.g., percentage)
|
||||||
|
- `userId` - Identifier of the user taking the test
|
||||||
|
- `secret` - Secret key for authentication and authorization
|
||||||
|
|
||||||
|
## Responses
|
||||||
|
|
||||||
|
- `200 OK` - The test has been added to the database sucessfully
|
||||||
|
- `401 UNAUTHORIZED` - The user has not been authenticated correctly
|
||||||
|
- `500 INTERNAL SERVER ERROR` - There has been a database error when attempting to
|
||||||
|
|
||||||
|
## Example Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://example.com/api/post_test" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"testType": "typing",
|
||||||
|
"testLength": 100,
|
||||||
|
"testTime": 600,
|
||||||
|
"testSeed": "random_seed_123",
|
||||||
|
"quoteId": "quote_456",
|
||||||
|
"wpm": 65.5,
|
||||||
|
"accuracy": 98.2,
|
||||||
|
"userId": "user_789",
|
||||||
|
"secret": "your_secret_key_here"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
@ -8,155 +8,102 @@
|
|||||||
* This class provides all the useful methods to interact with the api.
|
* This class provides all the useful methods to interact with the api.
|
||||||
*/
|
*/
|
||||||
class API {
|
class API {
|
||||||
|
|
||||||
constructor() { this.url = "/api"; }
|
constructor() { this.url = "/api"; }
|
||||||
|
|
||||||
/**
|
async createTest() {
|
||||||
* This takes the validated data and makes a post
|
const test = screenManager.screen.textbox.getLetters();
|
||||||
* request to the rocket server
|
|
||||||
* @param {String} testType
|
|
||||||
* @param {int} testLength
|
|
||||||
* @param {int} testTime
|
|
||||||
* @param {int} testSeed
|
|
||||||
* @param {int} quoteId
|
|
||||||
* @param {int} wpm
|
|
||||||
* @param {int} accuracy
|
|
||||||
* @param {int} userId
|
|
||||||
*/
|
|
||||||
postTest(pTestType, pTestLength, pTestTime, pTestSeed, pQuoteId, pWpm, pAccuracy, pUserId) {
|
|
||||||
const data = {
|
|
||||||
'test_type': pTestType,
|
|
||||||
'test_length': pTestLength,
|
|
||||||
'test_time': pTestTime,
|
|
||||||
'test_seed': pTestSeed,
|
|
||||||
'quote_id': pQuoteId,
|
|
||||||
'wpm': pWpm,
|
|
||||||
'accuracy': pAccuracy,
|
|
||||||
'user_id': pUserId,
|
|
||||||
'secret': user.secret
|
|
||||||
}
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open(
|
|
||||||
"POST",
|
|
||||||
`${this.url}/post_test/`
|
|
||||||
);
|
|
||||||
|
|
||||||
xhr.send(
|
|
||||||
JSON.stringify(data)
|
|
||||||
);
|
|
||||||
|
|
||||||
user.lastTest = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates all the parameters used for the postTest function which it then calls
|
|
||||||
*/
|
|
||||||
validateTest() {
|
|
||||||
const test = screenManager.screen.textbox.getWords();
|
|
||||||
const testType = "words";
|
const testType = "words";
|
||||||
let testLength = test.length;
|
const testLength = test.length;
|
||||||
let testTime = screenManager.screen.timer.getTime();
|
const testTime = screenManager.screen.timer.getTime();
|
||||||
const testSeed = 0;
|
const testSeed = 0;
|
||||||
const quoteId = 0;
|
const quoteId = 0;
|
||||||
let wpm;
|
|
||||||
const userId = Number(user.userId);
|
const userId = Number(user.userId);
|
||||||
let test_content = screenManager.screen.textbox.getTestContent();
|
const testContent = screenManager.screen.textbox.getTestContent();
|
||||||
|
|
||||||
let string = "";
|
let correctLetters = 0;
|
||||||
let inaccurateLetters = 0;
|
test.forEach((letter, index) => {
|
||||||
for (let letter = 0; letter < test.length; letter++) {
|
if (letter === testContent[index]) correctLetters++;
|
||||||
if (test[letter] === test_content[letter]) {
|
});
|
||||||
string += test[letter];
|
|
||||||
} else {
|
|
||||||
inaccurateLetters += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const accuracy = Math.round(((test.length - inaccurateLetters) / test.length) * 100);
|
const accuracy = Math.round((correctLetters / test.length) * 100);
|
||||||
|
const wpm = Math.round((correctLetters / 5) * (60 / testTime));
|
||||||
|
|
||||||
// this is the wpm calculation factoring in the time of test
|
const validations = [
|
||||||
// it assumes that all words are 5 characters long because on average
|
{value: testType, type: "string"},
|
||||||
// they are
|
{value: testLength, type: "number", min: 0},
|
||||||
wpm = Math.round((string.length / 5) * (60 / testTime));
|
{value: testTime, type: "number", min: 1},
|
||||||
|
{value: testSeed, type: "number", min: 0},
|
||||||
|
{value: quoteId, type: "number", min: 0},
|
||||||
|
{value: wpm, type: "number", min: 0},
|
||||||
|
{value: accuracy, type: "number", min: 0, max: 100},
|
||||||
|
{value: userId, type: "number", min: 0}
|
||||||
|
];
|
||||||
|
|
||||||
// the following code is a series of if statements that checks the
|
for (let {value, type, min, max} of validations) {
|
||||||
// types of the variables is correct if not it errors it and returns
|
if (typeof value !== type || value < min || (max !== undefined && value > max)) {
|
||||||
// out of the function
|
console.error(`Validation failed for value: ${value}, Type: ${type}, Min: ${min}, Max: ${max}`);
|
||||||
|
|
||||||
if ( typeof testType !== "string" ) {
|
|
||||||
console.error(`testType is value ${typeof testType}\nshould be a string`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( typeof testLength !== "number") {
|
|
||||||
console.error(`testLength is value ${typeof testLength}\n should be a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( typeof testTime !== "number") {
|
|
||||||
console.error(`testTime is value ${typeof testTime}\n should be a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( typeof testSeed !== "number") {
|
|
||||||
console.error(`testSeed is value ${typeof testSeed}\n should be a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( typeof quoteId !== "number") {
|
|
||||||
console.error(`quoteId is value ${typeof quoteId}\n should be a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( typeof wpm !== "number") {
|
|
||||||
console.error(`wpm is value ${typeof wpm}\n should be a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( typeof accuracy !== "number") {
|
|
||||||
console.error(`accuracy is value ${typeof accuracy}\n should be a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( typeof userId !== "number") {
|
|
||||||
console.error(`userId is value ${typeof userId}\n should be a number`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// after checking that all variables are of the correct type these if statements check
|
const data = {
|
||||||
// that they are acceptable values or are in acceptable bounds depending on variable types
|
test_type: testType,
|
||||||
|
test_length: testLength,
|
||||||
|
test_time: testTime,
|
||||||
|
test_seed: testSeed,
|
||||||
|
quote_id: quoteId,
|
||||||
|
wpm: wpm,
|
||||||
|
accuracy: accuracy,
|
||||||
|
user_id: userId,
|
||||||
|
secret: user.secret
|
||||||
|
};
|
||||||
|
|
||||||
if (testType !== "words") {
|
try {
|
||||||
// currently words is the only acceptable type but
|
const response = await fetch(`${this.url}/user/test`, {
|
||||||
// this will change in later iterations
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
console.error(`testType is invalid\nacceptable options ['words']`);
|
if (!response.ok) {
|
||||||
}
|
throw new Error('Network response was not ok');
|
||||||
// upper bounds for these numbers are less of a concern because the server will automatically
|
|
||||||
// return an error if values are over the limit
|
|
||||||
if (testLength < 0) {
|
|
||||||
console.error(`testLength is too small, min value 0`)
|
|
||||||
}
|
|
||||||
if (testTime < 1) {
|
|
||||||
console.error(`testTime is too small, min value 1`)
|
|
||||||
}
|
|
||||||
if (testSeed < 0) {
|
|
||||||
console.error(`testSeed is too small, min value 0`)
|
|
||||||
}
|
|
||||||
if (quoteId < 0) {
|
|
||||||
console.error(`quoteId is too small, min value 0`)
|
|
||||||
}
|
|
||||||
if (wpm < 0) {
|
|
||||||
console.error(`wpm is too small, min value 0`)
|
|
||||||
}
|
|
||||||
// accuracy needs an upper bound check because users can't have more than 100%
|
|
||||||
// accuracy when completing their tests
|
|
||||||
if (accuracy < 0) {
|
|
||||||
console.error(`accuracy is too small, min value 0`)
|
|
||||||
} else if (accuracy > 100) {
|
|
||||||
console.error(`accuracy is too big, max value 100`)
|
|
||||||
}
|
|
||||||
if (userId < 0) {
|
|
||||||
console.error(`userId is too small, min value 0`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// there will be other tests here in later iterations but for now these tests should suffice
|
// const responseData = await response.json();
|
||||||
|
// console.log(responseData);
|
||||||
|
user.lastTest = data; // Consider updating user.lastTest only on successful response
|
||||||
|
window.location.href = "/typing/pages/end/index.html"
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There has been a problem with your fetch operation:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.postTest(testType, testLength, testTime, testSeed, quoteId, wpm, accuracy, userId);
|
/**
|
||||||
|
* Fetches the latest test for a given user.
|
||||||
|
* @param {number} userId The user's ID.
|
||||||
|
* @param {string} secret The secret key for authentication.
|
||||||
|
* @returns {Promise<Object>} The latest test object or an error message.
|
||||||
|
*/
|
||||||
|
async getLatestUserTest() {
|
||||||
|
let userId = Number(user.userId);
|
||||||
|
let secret = user.secret;
|
||||||
|
const url = `${this.url}/user/test/${userId}/${secret}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
// Handle HTTP errors
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Successfully fetched latest test:", data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching the latest user test:", error);
|
||||||
|
return null; // or handle as needed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,7 +122,7 @@ class API {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open( "POST", `${this.url}/create_user/` );
|
xhr.open( "POST", `${this.url}/user/create/` );
|
||||||
|
|
||||||
xhr.send( JSON.stringify(user) );
|
xhr.send( JSON.stringify(user) );
|
||||||
|
|
||||||
@ -215,7 +162,7 @@ class API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', `${this.url}/login/${pUsername}/${pPassword}`);
|
xhr.open('GET', `${this.url}/user/login/${pUsername}/${pPassword}`);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
let response = JSON.parse(xhr.response);
|
let response = JSON.parse(xhr.response);
|
||||||
@ -253,29 +200,35 @@ class API {
|
|||||||
}
|
}
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.open('GET', `${this.url}/get_tests/${user.userId}/${user.secret}`);
|
xhr.open('GET', `${this.url}/user/tests/${user.userId}/${user.secret}`);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
user.tests = JSON.parse(xhr.response);
|
user.tests = JSON.parse(xhr.response);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getLeaderBoard() {
|
async getLeaderBoard() {
|
||||||
let xhr = new XMLHttpRequest();
|
try {
|
||||||
xhr.open('GET', `${this.url}/leaderboard/`);
|
const response = await fetch(`${this.url}/leaderboard/`);
|
||||||
xhr.send();
|
if (!response.ok) {
|
||||||
xhr.onload = () => {
|
throw new Error('Network response was not ok.');
|
||||||
user.leaderboard = JSON.parse(xhr.response);
|
}
|
||||||
};
|
user.leaderboard = await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch leaderboard:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getTest() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.url}/test/`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok.');
|
||||||
}
|
}
|
||||||
|
|
||||||
getTest() {
|
|
||||||
let xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', `${this.url}/new_test/`);
|
|
||||||
xhr.send();
|
|
||||||
xhr.onload = () =>{
|
|
||||||
const effectiveWidth = (windowWidth - 200) / 13;
|
const effectiveWidth = (windowWidth - 200) / 13;
|
||||||
let textArr = JSON.parse(xhr.response);
|
let textArr = await response.json();
|
||||||
let finalText = [];
|
let finalText = [];
|
||||||
let text = "";
|
let text = "";
|
||||||
for (let i = 0; i < textArr.length; i++) {
|
for (let i = 0; i < textArr.length; i++) {
|
||||||
@ -287,6 +240,8 @@ class API {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
user.nextTest = finalText;
|
user.nextTest = finalText;
|
||||||
};
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch a test:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
Index.css
|
|
||||||
Description: This file is the stylesheet for the html that the
|
|
||||||
user will see if they do not have javascript enabled.
|
|
||||||
Author: Arlo Filley
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background-color: #000000;
|
|
||||||
--text-color: yellow;
|
|
||||||
--font: Verdana, Geneva, Tahoma, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
position: absolute;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
noscript {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
font-family: var(--font);
|
|
||||||
color: var(--text-color);
|
|
||||||
font-size: large;
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This files is the root of the website.
|
|
||||||
* @author Arlo Filley
|
|
||||||
*/
|
|
||||||
|
|
||||||
// these are all of the globally accessible variables that are
|
|
||||||
// needed for the site to run correctly
|
|
||||||
let canvas, api, screenManager, user;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* loads the any assets before the setup function
|
|
||||||
* this allows p5.js to acess these assets including: sprites,
|
|
||||||
* fonts, etc
|
|
||||||
*/
|
|
||||||
function preload() {
|
|
||||||
roboto = loadFont('./assets/fonts/RobotoMono-Medium.ttf');
|
|
||||||
accountIconBlack = loadImage('./assets/icons/account_circle_black.svg');
|
|
||||||
accountIconWhite = loadImage('./assets/icons/account_circle_white.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* defines variables and sets up the p5.js canvas
|
|
||||||
* ready to be drawn with using the draw() function
|
|
||||||
*/
|
|
||||||
function setup() {
|
|
||||||
canvas = new Canvas();
|
|
||||||
canvas.resize();
|
|
||||||
canvas.center();
|
|
||||||
|
|
||||||
frameRate(60);
|
|
||||||
|
|
||||||
api = new API();
|
|
||||||
screenManager = new ScreenManager();
|
|
||||||
user = new User();
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
|
|
||||||
api.login(null, null, true);
|
|
||||||
api.getTest();
|
|
||||||
textFont(roboto);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* called once per frame. draws all other elements onto the canvas
|
|
||||||
* mostly will just call the screenManager.draw() method to make
|
|
||||||
* sure that the correct screen is being drawn
|
|
||||||
*/
|
|
||||||
function draw() {
|
|
||||||
background(user.colorScheme.background);
|
|
||||||
screenManager.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* called whenever a key is pressed, the variable key contains the
|
|
||||||
* key that the user last pressed
|
|
||||||
*/
|
|
||||||
function keyPressed() {
|
|
||||||
screenManager.letterTyped(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* called whenever the user resizes the window. Uses methods from the canvas wrapper class
|
|
||||||
* to resize and center the canvas such that it displays correctly
|
|
||||||
*/
|
|
||||||
function windowResized() {
|
|
||||||
canvas.resize();
|
|
||||||
canvas.center();
|
|
||||||
}
|
|
65
public/pages/account/index.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>User Tests</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav id="navbar">
|
||||||
|
<a href="/typing/pages/account/index.html">Account</a>
|
||||||
|
<a href="/typing/pages/test/index.html">Test</a>
|
||||||
|
<a href="/typing/pages/leaderboard/index.html">Leaderboard</a>
|
||||||
|
<a href="/typing/pages/test-settings/index.html">Test Settings</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>User Tests</h1>
|
||||||
|
<table id="userTestsTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Test Type</th>
|
||||||
|
<th>Test Length</th>
|
||||||
|
<th>Test Time</th>
|
||||||
|
<th>WPM</th>
|
||||||
|
<th>Accuracy</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="userTestsBody">
|
||||||
|
<!-- Table body will be filled by JavaScript -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function fetchUserTests() {
|
||||||
|
const userId = localStorage.getItem("userId"); // Replace with the actual user ID
|
||||||
|
const secret = localStorage.getItem("secret"); // Replace with the actual secret key
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user/tests/${userId}/${secret}`);
|
||||||
|
const tests = await response.json();
|
||||||
|
populateUserTests(tests);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user tests:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateUserTests(tests) {
|
||||||
|
const tableBody = document.getElementById('userTestsBody');
|
||||||
|
tableBody.innerHTML = ''; // Clear existing rows
|
||||||
|
tests.forEach(test => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${test.test_type}</td>
|
||||||
|
<td>${test.test_length}</td>
|
||||||
|
<td>${test.test_time}</td>
|
||||||
|
<td>${test.wpm ? test.wpm : 'N/A'}</td>
|
||||||
|
<td>${test.accuracy ? test.accuracy + '%' : 'N/A'}</td>
|
||||||
|
`;
|
||||||
|
tableBody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUserTests(); // Fetch and populate user tests when the page loads
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
22
public/pages/account/script.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
async function fetchLeaderboardData() {
|
||||||
|
const response = await fetch('/api/user/tests/');
|
||||||
|
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);
|
82
public/pages/account/styles.css
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 5%;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar {
|
||||||
|
background-color: #333; /* Navbar background color */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5%; /* Covering the top 20% of the screen */
|
||||||
|
z-index: 1000; /* Ensures the navbar is on top of other content */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Center horizontally */
|
||||||
|
align-items: center; /* Center vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff; /* Text color */
|
||||||
|
padding: 0.75% 20px; /* Padding for each link */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a:hover {
|
||||||
|
background-color: #555; /* Background color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a.active {
|
||||||
|
background-color: #555; /* Background color for active link */
|
||||||
|
}
|
26
public/pages/end/index.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>User Dashboard</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav id="navbar">
|
||||||
|
<a href="/typing/pages/account/index.html">Account</a>
|
||||||
|
<a href="/typing/pages/test/index.html">Test</a>
|
||||||
|
<a href="/typing/pages/leaderboard/index.html">Leaderboard</a>
|
||||||
|
<a href="/typing/pages/test-settings/index.html">Test Settings</a>
|
||||||
|
</nav>
|
||||||
|
<h1>User Dashboard</h1>
|
||||||
|
<div id="username">
|
||||||
|
<!-- Username will be displayed here -->
|
||||||
|
</div>
|
||||||
|
<h2>Most Recent Test Result</h2>
|
||||||
|
<div id="testResults">
|
||||||
|
<!-- Test results will be displayed here -->
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
69
public/pages/end/script.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Function to fetch the username
|
||||||
|
async function fetchUsername() {
|
||||||
|
const userId = localStorage.getItem("userId"); // Replace with the actual user ID
|
||||||
|
const secret = localStorage.getItem("secret")
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user/test/${userId}/${secret}`);
|
||||||
|
const userData = await response.json();
|
||||||
|
|
||||||
|
// Call function to display username
|
||||||
|
displayUsername(localStorage.getItem("username"));
|
||||||
|
displayTestResults(userData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching username:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to display test results in a table
|
||||||
|
function displayTestResults(testData) {
|
||||||
|
const testResultsDiv = document.getElementById('testResults');
|
||||||
|
|
||||||
|
if (!testData) {
|
||||||
|
testResultsDiv.innerHTML = '<p>No test data available.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct HTML for the table
|
||||||
|
const html = `
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Test Type</th>
|
||||||
|
<th>Test Length</th>
|
||||||
|
<th>Test Time</th>
|
||||||
|
<th>Test Seed</th>
|
||||||
|
<th>Quote ID</th>
|
||||||
|
<th>Words Per Minute (WPM)</th>
|
||||||
|
<th>Accuracy</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>${testData.test_type}</td>
|
||||||
|
<td>${testData.test_length}</td>
|
||||||
|
<td>${testData.test_time}</td>
|
||||||
|
<td>${testData.test_seed}</td>
|
||||||
|
<td>${testData.quote_id}</td>
|
||||||
|
<td>${testData.wpm}</td>
|
||||||
|
<td>${testData.accuracy}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Update the testResultsDiv with the HTML
|
||||||
|
testResultsDiv.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to display username
|
||||||
|
function displayUsername(username) {
|
||||||
|
const usernameDiv = document.getElementById('username');
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
usernameDiv.innerHTML = '<p>No username available.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the usernameDiv with the username
|
||||||
|
usernameDiv.innerHTML = `<p><strong>Welcome, ${username}!</strong></p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the fetchUsername function when the page loads
|
||||||
|
window.onload = fetchUsername;
|
85
public/pages/end/styles.css
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 5%;
|
||||||
|
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: larger;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
text-align: center;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar {
|
||||||
|
background-color: #333; /* Navbar background color */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5%; /* Covering the top 20% of the screen */
|
||||||
|
z-index: 1000; /* Ensures the navbar is on top of other content */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Center horizontally */
|
||||||
|
align-items: center; /* Center vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff; /* Text color */
|
||||||
|
padding: 0.75% 20px; /* Padding for each link */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a:hover {
|
||||||
|
background-color: #555; /* Background color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a.active {
|
||||||
|
background-color: #555; /* Background color for active link */
|
||||||
|
}
|
35
public/pages/leaderboard/index.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Leaderboard</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav id="navbar">
|
||||||
|
<a href="/typing/pages/account/index.html">Account</a>
|
||||||
|
<a href="/typing/pages/test/index.html">Test</a>
|
||||||
|
<a href="/typing/pages/leaderboard/index.html">Leaderboard</a>
|
||||||
|
<a href="/typing/pages/test-settings/index.html">Test Settings</a>
|
||||||
|
</nav>
|
||||||
|
<H1>Leaderboard</H1>
|
||||||
|
<table id="leaderboardTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>WPM</th>
|
||||||
|
<th>Accuracy (%)</th>
|
||||||
|
<th>Test Time (s)</th>
|
||||||
|
<th>Test Length (characters)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- Rows will be filled by JavaScript -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
82
public/pages/leaderboard/styles.css
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 5%;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar {
|
||||||
|
background-color: #333; /* Navbar background color */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5%; /* Covering the top 20% of the screen */
|
||||||
|
z-index: 1000; /* Ensures the navbar is on top of other content */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Center horizontally */
|
||||||
|
align-items: center; /* Center vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff; /* Text color */
|
||||||
|
padding: 0.75% 20px; /* Padding for each link */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a:hover {
|
||||||
|
background-color: #555; /* Background color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a.active {
|
||||||
|
background-color: #555; /* Background color for active link */
|
||||||
|
}
|
@ -7,7 +7,13 @@
|
|||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<H1>Leaderboard</H1>
|
<nav id="navbar">
|
||||||
|
<a href="/typing/pages/account/index.html">Account</a>
|
||||||
|
<a href="/typing/pages/test/index.html">Test</a>
|
||||||
|
<a href="/typing/pages/leaderboard/index.html">Leaderboard</a>
|
||||||
|
<a href="/typing/pages/test-settings/index.html">Test Settings</a>
|
||||||
|
</nav>
|
||||||
|
<H1>Test Settings</H1>
|
||||||
<button class="refresh-button" onclick="fetchLeaderboard()">⟳</button>
|
<button class="refresh-button" onclick="fetchLeaderboard()">⟳</button>
|
||||||
<a href="../index.html"><button>Go Back</button></a>
|
<a href="../index.html"><button>Go Back</button></a>
|
||||||
<table id="leaderboardTable">
|
<table id="leaderboardTable">
|
22
public/pages/test-settings/script.js
Normal file
@ -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);
|
@ -54,3 +54,29 @@ tr:nth-child(even) {
|
|||||||
fill: #90caf9;
|
fill: #90caf9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#navbar {
|
||||||
|
background-color: #333; /* Navbar background color */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5%; /* Covering the top 20% of the screen */
|
||||||
|
z-index: 1000; /* Ensures the navbar is on top of other content */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Center horizontally */
|
||||||
|
align-items: center; /* Center vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff; /* Text color */
|
||||||
|
padding: 0.75% 20px; /* Padding for each link */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a:hover {
|
||||||
|
background-color: #555; /* Background color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a.active {
|
||||||
|
background-color: #555; /* Background color for active link */
|
||||||
|
}
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
Before Width: | Height: | Size: 968 B After Width: | Height: | Size: 968 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 732 B After Width: | Height: | Size: 732 B |
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 752 B |
@ -17,12 +17,12 @@ class Canvas {
|
|||||||
|
|
||||||
|
|
||||||
center() {
|
center() {
|
||||||
this.canvas.position(this.x, this.y);
|
this.canvas.position(this.x, this.y + windowHeight / 100 * 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
resize() {
|
resize() {
|
||||||
this.canvas.resize(windowWidth, windowHeight);
|
this.canvas.resize(windowWidth, windowHeight - windowHeight / 100 * 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
@ -48,6 +48,7 @@ class Timer {
|
|||||||
this.timeElapsed = 0;
|
this.timeElapsed = 0;
|
||||||
this.ended;
|
this.ended;
|
||||||
this.hasStarted = false;
|
this.hasStarted = false;
|
||||||
|
this.hasEnded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getX() {
|
getX() {
|
||||||
@ -163,7 +164,7 @@ class Timer {
|
|||||||
*/
|
*/
|
||||||
tick() {
|
tick() {
|
||||||
this.timeElapsed = (millis() - this.startTime) / 1000;
|
this.timeElapsed = (millis() - this.startTime) / 1000;
|
||||||
if (this.timeElapsed >= this.time) {
|
if (this.timeElapsed >= this.time && !this.testEnded) {
|
||||||
this.end();
|
this.end();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -172,14 +173,13 @@ class Timer {
|
|||||||
* this function is called at the end of the timer
|
* this function is called at the end of the timer
|
||||||
*/
|
*/
|
||||||
end() {
|
end() {
|
||||||
|
api.createTest()
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
api.validateTest();
|
|
||||||
this.timeElapsed = 0;
|
this.timeElapsed = 0;
|
||||||
this.time = 0;
|
this.time = 0;
|
||||||
api.getTest();
|
|
||||||
// Then this function will call all other functions necessary to complete the test
|
// Then this function will call all other functions necessary to complete the test
|
||||||
// this will likely including changing the screen and interacting with the api
|
// this will likely including changing the screen and interacting with the api
|
||||||
screenManager.setScreen(new EndScreen());
|
this.hasEnded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,6 +188,7 @@ class Timer {
|
|||||||
draw() {
|
draw() {
|
||||||
// if the time shouldn't be rendered it quickly exits out of this method
|
// if the time shouldn't be rendered it quickly exits out of this method
|
||||||
if (!this.visible) return;
|
if (!this.visible) return;
|
||||||
|
if (this.hasEnded) return;
|
||||||
textAlign(LEFT);
|
textAlign(LEFT);
|
||||||
|
|
||||||
// adds a border for the bar if one is needed
|
// adds a border for the bar if one is needed
|
74
public/pages/test/index.css
Executable file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
Index.css
|
||||||
|
Description: This file is the stylesheet for the html that the
|
||||||
|
user will see if they do not have javascript enabled.
|
||||||
|
Author: Arlo Filley
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background-color: #000000;
|
||||||
|
--text-color: yellow;
|
||||||
|
--font: Verdana, Geneva, Tahoma, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
noscript {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
font-family: var(--font);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar {
|
||||||
|
background-color: #333; /* Navbar background color */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5%; /* Covering the top 20% of the screen */
|
||||||
|
z-index: 1000; /* Ensures the navbar is on top of other content */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Center horizontally */
|
||||||
|
align-items: center; /* Center vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff; /* Text color */
|
||||||
|
padding: 0.7% 20px; /* Padding for each link */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a:hover {
|
||||||
|
background-color: #555; /* Background color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a.active {
|
||||||
|
background-color: #555; /* Background color for active link */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>TypeFast</title>
|
<title>Typing - Test</title>
|
||||||
|
|
||||||
<!-- CSS Files -->
|
<!-- CSS Files -->
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css">
|
||||||
@ -15,6 +15,13 @@
|
|||||||
<link rel="icon" type="image/png" sizes="16x16" href="./assets/favicon/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="./assets/favicon/favicon-16x16.png">
|
||||||
<link rel="manifest" href="./assets/favicon/site.webmanifest">
|
<link rel="manifest" href="./assets/favicon/site.webmanifest">
|
||||||
|
|
||||||
|
<nav id="navbar">
|
||||||
|
<a href="/typing/pages/account/index.html">Account</a>
|
||||||
|
<a href="/typing/pages/test/index.html">Test</a>
|
||||||
|
<a href="/typing/pages/leaderboard/index.html">Leaderboard</a>
|
||||||
|
<a href="/typing/pages/test-settings/index.html">Test Settings</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<!-- Main Script Files -->
|
<!-- Main Script Files -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
|
||||||
<!-- <script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.min.js"></script> -->
|
<!-- <script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.min.js"></script> -->
|
||||||
@ -31,19 +38,11 @@
|
|||||||
|
|
||||||
<!-- Screen Files-->
|
<!-- Screen Files-->
|
||||||
<script src="./screens/screenmanager.js" defer="true"></script>
|
<script src="./screens/screenmanager.js" defer="true"></script>
|
||||||
<script src="./screens/startscreen.js" defer="true"></script>
|
|
||||||
<script src="./screens/testscreen.js" defer="true"></script>
|
<script src="./screens/testscreen.js" defer="true"></script>
|
||||||
<script src="./screens/endscreen.js" defer="true"></script>
|
|
||||||
<script src="./screens/accountScreen.js" defer="true"></script>
|
|
||||||
<script src="./screens/signUpScreen.js" defer="true"></script>
|
|
||||||
<script src="./screens/loginscreen.js" defer="true"></script>
|
|
||||||
<script src="./screens/profilescreen.js" defer="true"></script>
|
|
||||||
<script src="./screens/leaderboardscreen.js" defer="true"></script>
|
|
||||||
<script src="./screens/settingsScreen.js" defer="true"></script>
|
|
||||||
|
|
||||||
<!-- API Script Files -->
|
<!-- API Script Files -->
|
||||||
<script src="./api/api.js" defer="true"></script>
|
<script src="/typing/api/api.js" defer="true"></script>
|
||||||
<script src="./api/user.js" defer="true"></script>
|
<script src="/typing/api/user.js" defer="true"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>Please Enable Javascript</noscript>
|
<noscript>Please Enable Javascript</noscript>
|
72
public/pages/test/index.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @file This file is the root of the website.
|
||||||
|
* @author Arlo Filley
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads any assets before the setup function.
|
||||||
|
* This allows p5.js to access these assets including: sprites,
|
||||||
|
* fonts, etc.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function preload() {
|
||||||
|
roboto = loadFont('./assets/fonts/RobotoMono-Medium.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines variables and sets up the p5.js canvas
|
||||||
|
* ready to be drawn with using the draw() function.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
async function setup() {
|
||||||
|
canvas = new Canvas();
|
||||||
|
canvas.resize();
|
||||||
|
canvas.center();
|
||||||
|
|
||||||
|
frameRate(60);
|
||||||
|
|
||||||
|
api = new API();
|
||||||
|
screenManager = new ScreenManager();
|
||||||
|
/**
|
||||||
|
* @type {User}
|
||||||
|
*/
|
||||||
|
user = new User();
|
||||||
|
await api.getTest();
|
||||||
|
console.log(user.nextTest);
|
||||||
|
screenManager.setScreen(new TestScreen());
|
||||||
|
|
||||||
|
api.login(null, null, true);
|
||||||
|
textFont(roboto);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once per frame. Draws all other elements onto the canvas.
|
||||||
|
* Mostly will just call the screenManager.draw() method to make
|
||||||
|
* sure that the correct screen is being drawn.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function draw() {
|
||||||
|
background(user.colorScheme.background);
|
||||||
|
screenManager.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever a key is pressed. The variable key contains the
|
||||||
|
* key that the user last pressed.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function keyPressed() {
|
||||||
|
screenManager.letterTyped(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever the user resizes the window. Uses methods from the canvas wrapper class
|
||||||
|
* to resize and center the canvas such that it displays correctly.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function windowResized() {
|
||||||
|
canvas.resize();
|
||||||
|
canvas.center();
|
||||||
|
}
|
@ -31,11 +31,17 @@ class TestScreen {
|
|||||||
}
|
}
|
||||||
this.stopButton.draw();
|
this.stopButton.draw();
|
||||||
if (this.stopButton.isPressed()) {
|
if (this.stopButton.isPressed()) {
|
||||||
screenManager.setScreen(new StartScreen())
|
window.location.href = "/typing/pages/leaderboard/index.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timer.hasEnded) {
|
||||||
|
this.stopButton.y = 0
|
||||||
|
text("Test Has Ended", 100, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
letterTyped(key) {
|
letterTyped(key) {
|
||||||
|
if (this.timer.hasEnded) return;
|
||||||
this.textbox.letterTyped(key);
|
this.textbox.letterTyped(key);
|
||||||
if (!this.timerStarted) {
|
if (!this.timerStarted) {
|
||||||
this.timer.start();
|
this.timer.start();
|
@ -1,115 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file provides a screen for the user to login through
|
|
||||||
* @author Arlo Filley
|
|
||||||
*
|
|
||||||
* TODO:
|
|
||||||
* - move into an seperated account page with signup and logout
|
|
||||||
* - make passwords not display plain text
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this class displays a number of textboxes that allows the user to input a
|
|
||||||
* username and password. Then find out the user_id of that account through the
|
|
||||||
* necessary api routes.
|
|
||||||
*/
|
|
||||||
class AccountScreen {
|
|
||||||
constructor() {
|
|
||||||
this.textboxes = [
|
|
||||||
new Textbox(
|
|
||||||
120, 350, 500, 100, 0, true, "#000", false,
|
|
||||||
"#000", "#000", true
|
|
||||||
),
|
|
||||||
|
|
||||||
new Textbox(
|
|
||||||
120, 500, 500, 100, 0, true, "#000", false,
|
|
||||||
"#000", "#000", false, false, true
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
this.buttons = [
|
|
||||||
new Button(windowWidth / 2 - 400, 300, 500, 100, "", false, true, "#000", "#000", "#fff", "#000", "#000", "#fff"),
|
|
||||||
new Button(windowWidth / 2 - 400, 450, 500, 100, "", false, true, "#000", "#000", "#fff", "#000", "#000", "#fff"),
|
|
||||||
new Button(windowWidth / 2 + 200, 300, 100, 50, "Login"),
|
|
||||||
new Button(windowWidth / 2 + 200, 400, 100, 50, "Sign up"),
|
|
||||||
new Button(windowWidth / 2 + 200, 500, 100, 50, "Logout"),
|
|
||||||
]
|
|
||||||
|
|
||||||
this.menu = new Menu();
|
|
||||||
|
|
||||||
// keeps track of which textbox the user last clicked on
|
|
||||||
this.activeTextBox = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the SignUpScreen class with all
|
|
||||||
* appropriate elements
|
|
||||||
*/
|
|
||||||
draw() {
|
|
||||||
textSize(100);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Account", 0, 100, windowWidth, 110);
|
|
||||||
for (let i = 0; i < this.buttons.length; i++) {
|
|
||||||
this.buttons[i].draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < this.textboxes.length; i++) {
|
|
||||||
this.textboxes[i].draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
textSize(30);
|
|
||||||
text("Username", windowWidth / 2 - 400, 275);
|
|
||||||
text("Password", windowWidth / 2 - 400, 425);
|
|
||||||
|
|
||||||
if (this.buttons[0].isPressed()) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=0;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (this.buttons[1].isPressed()) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=1;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (this.buttons[2].isPressed()) {
|
|
||||||
api.login(
|
|
||||||
this.textboxes[0].getWords(),
|
|
||||||
this.textboxes[1].getWords()
|
|
||||||
)
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
} else if (this.buttons[3].isPressed()) {
|
|
||||||
api.createUser(
|
|
||||||
this.textboxes[0].getWords(),
|
|
||||||
this.textboxes[1].getWords()
|
|
||||||
)
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
} else if (this.buttons[4].isPressed()) {
|
|
||||||
api.logout();
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
}
|
|
||||||
this.menu.draw();
|
|
||||||
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {key} key
|
|
||||||
*/
|
|
||||||
letterTyped(key) {
|
|
||||||
if (key === "Tab" && this.activeTextBox === 0) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=1;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (key === "Tab" && this.activeTextBox === 1) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=0;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (key === "Enter") {
|
|
||||||
api.login(
|
|
||||||
this.textboxes[0].getWords(),
|
|
||||||
this.textboxes[1].getWords()
|
|
||||||
)
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
} else {
|
|
||||||
this.textboxes[this.activeTextBox].letterTyped(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file provides a screen class that can be displayed at the end of a test
|
|
||||||
* @author Arlo Filley
|
|
||||||
*
|
|
||||||
* TODO:
|
|
||||||
* - provide the user with the data of the test that they have just
|
|
||||||
* completed, such as their wpm, accuracy, etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is for a screen that is displayed at the end of a test,
|
|
||||||
* currently it just tells the user to press start to enter another test
|
|
||||||
*/
|
|
||||||
class EndScreen {
|
|
||||||
constructor() {
|
|
||||||
this.menu = new Menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
textSize(100);
|
|
||||||
textAlign(CENTER, CENTER);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Test Complete", 0, 0, windowWidth - 100, windowHeight / 6);
|
|
||||||
|
|
||||||
textSize(30);
|
|
||||||
text(`${user.lastTest.wpm} words per minute`, windowWidth / 2, 200);
|
|
||||||
text(`${user.lastTest.accuracy}% accuracy`, windowWidth / 2, 240);
|
|
||||||
text(`${user.lastTest.test_length} characters typed`, windowWidth / 2, 280);
|
|
||||||
text(`${user.lastTest.test_time}s`, windowWidth / 2, 320);
|
|
||||||
this.menu.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
letterTyped(key) {
|
|
||||||
if (key === "Enter") screenManager.setScreen(new TestScreen());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file provides a leaderboard for the user to compare times.
|
|
||||||
* @author Arlo Filley
|
|
||||||
*
|
|
||||||
* TODO:
|
|
||||||
* - implement a way for the user to scroll down the tests.
|
|
||||||
* - display more tests on the screen at once, up to 15
|
|
||||||
* - store the leaderboard in localstorage as a cache for the most recent results
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this class is a screen which shows the current leaderboard from the
|
|
||||||
* results gotten via the api.
|
|
||||||
*/
|
|
||||||
class LeaderboardScreen {
|
|
||||||
constructor() {
|
|
||||||
this.menu = new Menu();
|
|
||||||
api.getLeaderBoard();
|
|
||||||
this.testButtons;
|
|
||||||
// this.buttons = [
|
|
||||||
// new Button(1150, 270, 240, 120, "up"),
|
|
||||||
// new Button(1150, 390, 240, 120, "down"),
|
|
||||||
// ]
|
|
||||||
this.offset = 0;
|
|
||||||
this.scroll_bar_button = new Button(1200, 200, 20, 20, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
textSize(100);
|
|
||||||
textAlign(CENTER, CENTER);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Leaderboard", 0, 100, windowWidth, 120);
|
|
||||||
this.menu.draw();
|
|
||||||
|
|
||||||
textSize(20);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
if (user.leaderboard != undefined) {
|
|
||||||
if (this.testButtons === undefined) {
|
|
||||||
this.createTestButtons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fill(user.colorScheme.testBad);
|
|
||||||
rect(1200, 270, 20, 420);
|
|
||||||
|
|
||||||
fill(user.colorScheme.testGood);
|
|
||||||
rect(1200, 270, 20, 420 / user.leaderboard.length * (this.offset + 1));
|
|
||||||
this.scroll_bar_button.height = (user.leaderboard.length)
|
|
||||||
|
|
||||||
if (this.scroll_bar_button.isPressed()) {
|
|
||||||
this.scroll_bar_button.y = mouseY - this.scroll_bar_button.height / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the furthest up the scrollbar can go is the top
|
|
||||||
if (this.scroll_bar_button.y < 270) {
|
|
||||||
this.scroll_bar_button.y = 270;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scroll_bar_button.y > 690 - this.scroll_bar_button.height) {
|
|
||||||
this.scroll_bar_button.y = 690 - this.scroll_bar_button.height;
|
|
||||||
}
|
|
||||||
this.scroll_bar_button.draw();
|
|
||||||
|
|
||||||
if (this.testButtons !== undefined && this.testButtons.length > 1) {
|
|
||||||
for (let i = 0; i < this.testButtons.length; i++) {
|
|
||||||
this.testButtons[i][0].draw()
|
|
||||||
this.testButtons[i][1].draw()
|
|
||||||
this.testButtons[i][2].draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.offset = Number((
|
|
||||||
// number of pixels from top of screen / total range of options, put to a whole integer to produce the correct offset
|
|
||||||
(this.scroll_bar_button.y - 270) / ((420 - this.scroll_bar_button.height) / (user.leaderboard.length - 13))
|
|
||||||
).toFixed(0));
|
|
||||||
|
|
||||||
this.createTestButtons(this.offset)
|
|
||||||
} else {
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Looks Like There Isn't A Leaderboard", windowWidth / 2, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
createTestButtons(offset = 0) {
|
|
||||||
this.testButtons = [[
|
|
||||||
new Button(400, 270, 100, 30, "ranking"), // test # button
|
|
||||||
new Button(500, 270, 400, 30, "username"), // wpm button
|
|
||||||
new Button(900, 270, 240, 30, "words per minute"), // accuracy button
|
|
||||||
]];
|
|
||||||
let j = 300;
|
|
||||||
for (let i = 0 + offset; i < user.leaderboard.length && i <= 12+offset; i++) {
|
|
||||||
this.testButtons.push([
|
|
||||||
new Button(400, j, 100, 30, `${i+1}`, true, true, "#000", "#000", "#fff"), // test # button
|
|
||||||
new Button(500, j, 400, 30, `${user.leaderboard[i].username}`, true, true, "#000", "#000", "#fff"), // accuracy button
|
|
||||||
new Button(900, j, 240, 30, `${user.leaderboard[i].wpm}`, true, true, "#000", "#000", "#fff"), // wpm button
|
|
||||||
])
|
|
||||||
j+=30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file provides a screen for the user to login through
|
|
||||||
* @author Arlo Filley
|
|
||||||
*
|
|
||||||
* TODO:
|
|
||||||
* - move into an seperated account page with signup and logout
|
|
||||||
* - make passwords not display plain text
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this class displays a number of textboxes that allows the user to input a
|
|
||||||
* username and password. Then find out the user_id of that account through the
|
|
||||||
* necessary api routes.
|
|
||||||
*/
|
|
||||||
class LoginScreen {
|
|
||||||
constructor() {
|
|
||||||
this.textboxes = [
|
|
||||||
new Textbox(
|
|
||||||
120, 250, 500, 100, 0, true, user.colorScheme.text, false,
|
|
||||||
"#000", "#000", true, false
|
|
||||||
),
|
|
||||||
|
|
||||||
new Textbox(
|
|
||||||
120, 400, 500, 100, 0, true, user.colorScheme.text, false,
|
|
||||||
"000", "#000", false, false
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
this.buttons = [
|
|
||||||
new Button(
|
|
||||||
100, 200, 500, 100, 0, true, user.colorScheme.buttonBG, false,
|
|
||||||
"#000", "#fff", ""
|
|
||||||
),
|
|
||||||
|
|
||||||
new Button(
|
|
||||||
100, 350, 500, 100, 0, true, "#000", false,
|
|
||||||
"#000", "#fff", ""
|
|
||||||
),
|
|
||||||
|
|
||||||
new Button(
|
|
||||||
700, 300, 100, 50, 0, true, "#000", false,
|
|
||||||
"#000", "#00ff00", "Login"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
this.menu = new Menu();
|
|
||||||
|
|
||||||
// keeps track of which textbox the user last clicked on
|
|
||||||
this.activeTextBox = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the SignUpScreen class with all
|
|
||||||
* appropriate elements
|
|
||||||
*/
|
|
||||||
draw() {
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Username", 110, 175);
|
|
||||||
text("Password", 110, 325);
|
|
||||||
|
|
||||||
for (let i = 0; i < this.buttons.length; i++) {
|
|
||||||
this.buttons[i].draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < this.textboxes.length; i++) {
|
|
||||||
this.textboxes[i].draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.buttons[0].isPressed()) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=0;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (this.buttons[1].isPressed()) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=1;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (this.buttons[2].isPressed()) {
|
|
||||||
api.login(this.textboxes[0].getWords(), this.textboxes[1].getWords())
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.menu.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {key} key
|
|
||||||
*/
|
|
||||||
letterTyped(key) {
|
|
||||||
if (key === "Tab" && this.activeTextBox === 0) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox = 1;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (key === "Tab" && this.activeTextBox === 1) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox = 0;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (key === "Enter") {
|
|
||||||
api.login(
|
|
||||||
this.textboxes[0].getWords(),
|
|
||||||
this.textboxes[1].getWords()
|
|
||||||
)
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
} else {
|
|
||||||
this.textboxes[this.activeTextBox].letterTyped(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file provides the user with their profilescreen, where they can see their own tests
|
|
||||||
* @author Arlo Filley
|
|
||||||
*
|
|
||||||
* TODO:
|
|
||||||
* - change button name
|
|
||||||
* - provide filters for tests
|
|
||||||
* - implement a way to scroll through tests
|
|
||||||
* - create a way to have personal bests and track them
|
|
||||||
* - store tests in localstorage.
|
|
||||||
* - show user tests even if they are not logged in
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class displays all of the test data for a given user
|
|
||||||
*/
|
|
||||||
class ProfileScreen {
|
|
||||||
constructor() {
|
|
||||||
this.menu = new Menu();
|
|
||||||
api.getUserTests();
|
|
||||||
this.testButtons;
|
|
||||||
// this.buttons = [
|
|
||||||
// new Button(950, 270, 240, 120, "up"),
|
|
||||||
// new Button(950, 390, 240, 120, "down"),
|
|
||||||
// ]
|
|
||||||
this.offset = 0;
|
|
||||||
this.scroll_bar_button = new Button(1200, 200, 20, 20, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
textSize(100);
|
|
||||||
textAlign(CENTER, CENTER);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Profile", 0, 100, windowWidth, 120);
|
|
||||||
|
|
||||||
this.menu.draw();
|
|
||||||
|
|
||||||
textSize(20);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
if (user.tests != undefined) {
|
|
||||||
if (this.testButtons === undefined) {
|
|
||||||
this.createTestButtons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.testButtons !== undefined && this.testButtons.length > 1) {
|
|
||||||
for (let i = 0; i < this.testButtons.length; i++) {
|
|
||||||
this.testButtons[i][0].draw()
|
|
||||||
this.testButtons[i][1].draw()
|
|
||||||
this.testButtons[i][2].draw()
|
|
||||||
this.testButtons[i][3].draw()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Looks Like You Don't have any tests :(", windowWidth / 2, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
|
|
||||||
if (user.tests === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fill(user.colorScheme.testBad);
|
|
||||||
rect(1200, 270, 20, 420);
|
|
||||||
|
|
||||||
fill(user.colorScheme.testGood);
|
|
||||||
rect(1200, 270, 20, 420 / user.tests.length * (this.offset + 1));
|
|
||||||
this.scroll_bar_button.height = (user.tests.length)
|
|
||||||
|
|
||||||
if (this.scroll_bar_button.isPressed()) {
|
|
||||||
this.scroll_bar_button.y = mouseY - this.scroll_bar_button.height / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// the furthest up the scrollbar can go is the top
|
|
||||||
if (this.scroll_bar_button.y < 270) {
|
|
||||||
this.scroll_bar_button.y = 270;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scroll_bar_button.y > 690 - this.scroll_bar_button.height) {
|
|
||||||
this.scroll_bar_button.y = 690 - this.scroll_bar_button.height;
|
|
||||||
}
|
|
||||||
this.scroll_bar_button.draw();
|
|
||||||
|
|
||||||
this.offset = Number((
|
|
||||||
// number of pixels from top of screen / total range of options, put to a whole integer to produce the correct offset
|
|
||||||
(this.scroll_bar_button.y - 270) / ((420 - this.scroll_bar_button.height) / (user.tests.length - 13))
|
|
||||||
).toFixed(0));
|
|
||||||
this.createTestButtons(this.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
createTestButtons(offset = 0) {
|
|
||||||
this.testButtons = [[
|
|
||||||
new Button(600, 270, 100, 30, "test #"), // test # button
|
|
||||||
new Button(700, 270, 100, 30, "wpm"), // wpm button
|
|
||||||
new Button(800, 270, 100, 30, "accuracy"), // accuracy button
|
|
||||||
new Button(900, 270, 240, 30, "characters typed")
|
|
||||||
]];
|
|
||||||
let j = 300;
|
|
||||||
for (let i = user.tests.length-1-offset; i >= user.tests.length-13-offset && i >= 0; i--) {
|
|
||||||
this.testButtons.push([
|
|
||||||
new Button(600, j, 100, 30, `${i+1}`, true, true , "#000", "#000", "#fff"), // test # button
|
|
||||||
new Button(700, j, 100, 30, `${user.tests[i].wpm}`, true, true , "#000", "#000", "#fff"), // accuracy button
|
|
||||||
new Button(800, j, 100, 30, `${user.tests[i].accuracy}`, true, true , "#000", "#000", "#fff"), // wpm button
|
|
||||||
new Button(900, j, 240, 30, `${user.tests[i].test_length}`, true, true , "#000", "#000", "#fff")
|
|
||||||
])
|
|
||||||
j+=30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file provides a screen where the user can edit the settings of their tests
|
|
||||||
* @author Arlo Filley
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides all of the necessary settings for the user to be able to edit test settings
|
|
||||||
*/
|
|
||||||
class SettingsScreen {
|
|
||||||
constructor() {
|
|
||||||
this.menu = new Menu();
|
|
||||||
this.timeMenu = new TimeMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
textAlign(CENTER, CENTER);
|
|
||||||
|
|
||||||
textSize(100);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Test Settings", 0, 100, windowWidth, 110);
|
|
||||||
|
|
||||||
this.menu.draw();
|
|
||||||
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Test Duration", windowWidth / 2 - 250, 265)
|
|
||||||
this.timeMenu.draw();
|
|
||||||
fill("#000");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file provides a way for the user to sign up for an account
|
|
||||||
* @author Arlo Filley
|
|
||||||
*
|
|
||||||
* TODO:
|
|
||||||
* - move into an seperated account page with signup and logout
|
|
||||||
* - make passwords not display plain text
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides the textboxes and methods necessary for a user
|
|
||||||
* to sign up for a new account, which it should then log them into
|
|
||||||
*/
|
|
||||||
class SignUpScreen {
|
|
||||||
constructor() {
|
|
||||||
this.textboxes = [
|
|
||||||
new Textbox(
|
|
||||||
120, 250, 500, 100,0, true, "#000", false,
|
|
||||||
"#000", "#000", true
|
|
||||||
),
|
|
||||||
|
|
||||||
new Textbox(
|
|
||||||
120, 400, 500, 100, 0, true, "#000", false,
|
|
||||||
"000", "#000", false
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
this.buttons = [
|
|
||||||
new Button(
|
|
||||||
100, 200, 500, 100, 0, true, "#000", false,
|
|
||||||
"#000", "#fff", ""
|
|
||||||
),
|
|
||||||
|
|
||||||
new Button(
|
|
||||||
100, 350, 500, 100, 0, true, "#000", false,
|
|
||||||
"#000", "#fff", ""
|
|
||||||
),
|
|
||||||
|
|
||||||
new Button(
|
|
||||||
700, 300, 100, 50, 0, true, "#000", false,
|
|
||||||
"#000", "#00ff00", "Sign Up"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
this.menu = new Menu();
|
|
||||||
|
|
||||||
this.activeTextBox = 0
|
|
||||||
// keeps track of which textbox the user last clicked on
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the SignUpScreen class with all
|
|
||||||
* appropriate elements
|
|
||||||
*/
|
|
||||||
draw() {
|
|
||||||
for (let i = 0; i < this.buttons.length; i++) {
|
|
||||||
this.buttons[i].draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < this.textboxes.length; i++) {
|
|
||||||
this.textboxes[i].draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Username", 110, 175);
|
|
||||||
text("Password", 110, 325);
|
|
||||||
|
|
||||||
if (this.buttons[0].isPressed()) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=0;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (this.buttons[1].isPressed()) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=1;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (this.buttons[2].isPressed()) {
|
|
||||||
api.createUser(
|
|
||||||
this.textboxes[0].getWords(),
|
|
||||||
this.textboxes[1].getWords()
|
|
||||||
)
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
}
|
|
||||||
this.menu.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {key} key
|
|
||||||
*/
|
|
||||||
letterTyped(key) {
|
|
||||||
if (key === "Tab" && this.activeTextBox === 0) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=1;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (key === "Tab" && this.activeTextBox === 1) {
|
|
||||||
this.textboxes[this.activeTextBox].line = false;
|
|
||||||
this.activeTextBox=0;
|
|
||||||
this.textboxes[this.activeTextBox].line = true;
|
|
||||||
} else if (key === "Enter") {
|
|
||||||
api.createUser(
|
|
||||||
this.textboxes[0].getWords(),
|
|
||||||
this.textboxes[1].getWords()
|
|
||||||
)
|
|
||||||
screenManager.setScreen(new StartScreen());
|
|
||||||
} else {
|
|
||||||
this.textboxes[this.activeTextBox].letterTyped(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file This file is the base screen when the user visits the site
|
|
||||||
* @author Arlo Filley
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This screen class is the base screen. It provides the user with basic instructions
|
|
||||||
* and a set of menus to navigate the site
|
|
||||||
*/
|
|
||||||
class StartScreen {
|
|
||||||
constructor() {
|
|
||||||
this.menu = new Menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
textSize(100);
|
|
||||||
textAlign(CENTER, CENTER);
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
text("Press enter to start test", 0, 0, windowWidth, windowHeight);
|
|
||||||
|
|
||||||
this.menu.draw();
|
|
||||||
|
|
||||||
fill(user.colorScheme.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
letterTyped(key) {
|
|
||||||
if (key === "Enter") {
|
|
||||||
screenManager.setScreen(new TestScreen());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,3 +2,4 @@ pub mod leaderboard;
|
|||||||
pub mod sql;
|
pub mod sql;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
pub mod ping;
|
17
src/api/ping.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/// # Ping Endpoint
|
||||||
|
/// ## GET `/api/ping`
|
||||||
|
/// Shows whether the api is up and running. Accessible from http://url/api/ping
|
||||||
|
/// ## Responses
|
||||||
|
/// - `200 OK`: The api is up and running
|
||||||
|
/// ## Example Request
|
||||||
|
/// ```bash
|
||||||
|
/// curl -X GET "https://url/api/ping"
|
||||||
|
/// ```
|
||||||
|
/// ## Example Response
|
||||||
|
/// ```
|
||||||
|
/// "Hello World! I'm A rocket Webserver"
|
||||||
|
/// ```
|
||||||
|
#[get("/ping")]
|
||||||
|
pub fn ping() -> &'static str {
|
||||||
|
"Hello World! I'm A rocket Webserver"
|
||||||
|
}
|
@ -13,7 +13,7 @@
|
|||||||
use rocket::serde::{json::Json, Serialize};
|
use rocket::serde::{json::Json, Serialize};
|
||||||
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||||
|
|
||||||
use crate::api::test::PostTest;
|
use crate::api::user::test::PostTest;
|
||||||
|
|
||||||
/// Contains the database connection pool
|
/// Contains the database connection pool
|
||||||
pub struct Database(SqlitePool);
|
pub struct Database(SqlitePool);
|
||||||
@ -141,25 +141,17 @@ impl Database {
|
|||||||
|
|
||||||
/// returns all the tests that a given user_id has
|
/// returns all the tests that a given user_id has
|
||||||
/// completed from the database
|
/// completed from the database
|
||||||
pub async fn get_user_tests(
|
pub async fn get_user_tests(&self, user_id: u32, secret: &str) -> Result<Vec<Test>, sqlx::Error> {
|
||||||
&self,
|
let tests = sqlx::query!("
|
||||||
user_id: u32,
|
|
||||||
secret: &str,
|
|
||||||
) -> Result<Vec<Test>, sqlx::Error> {
|
|
||||||
let tests = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT test_type, test_length, test_time, test_seed, quote_id, wpm, accuracy
|
SELECT test_type, test_length, test_time, test_seed, quote_id, wpm, accuracy
|
||||||
FROM tests
|
FROM tests
|
||||||
INNER JOIN users ON users.user_id = tests.user_id
|
INNER JOIN users ON users.user_id = tests.user_id
|
||||||
WHERE users.user_id=? AND users.secret=?",
|
WHERE users.user_id=? AND users.secret=?",
|
||||||
user_id,
|
user_id, secret
|
||||||
secret
|
|
||||||
)
|
)
|
||||||
.fetch_all(&self.0)
|
.fetch_all(&self.0)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("{}", tests.len());
|
|
||||||
|
|
||||||
let user_tests = tests
|
let user_tests = tests
|
||||||
.iter()
|
.iter()
|
||||||
.map(|test| Test {
|
.map(|test| Test {
|
||||||
@ -176,12 +168,35 @@ impl Database {
|
|||||||
Ok(user_tests)
|
Ok(user_tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_test(&self, user_id: u32) -> Result<Test, sqlx::Error> {
|
||||||
|
let test = sqlx::query!("
|
||||||
|
SELECT test_type, test_length, test_time, test_seed, quote_id, wpm, accuracy
|
||||||
|
FROM tests
|
||||||
|
INNER JOIN users ON users.user_id = tests.user_id
|
||||||
|
WHERE users.user_id=?
|
||||||
|
ORDER BY test_id DESC
|
||||||
|
LIMIT 1;",
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_one(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user_tests = Test {
|
||||||
|
test_type: test.test_type.clone(),
|
||||||
|
test_length: test.test_length.unwrap() as u32,
|
||||||
|
test_time: test.test_time.unwrap() as u32,
|
||||||
|
test_seed: test.test_seed.unwrap(),
|
||||||
|
quote_id: test.quote_id.unwrap() as i32,
|
||||||
|
wpm: test.wpm.unwrap() as u8,
|
||||||
|
accuracy: test.accuracy.unwrap() as u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(user_tests)
|
||||||
|
}
|
||||||
|
|
||||||
/// returns a vector of leaderboard tests, where each one is the fastest words
|
/// returns a vector of leaderboard tests, where each one is the fastest words
|
||||||
/// per minute that a given user has achieved
|
/// per minute that a given user has achieved
|
||||||
pub async fn get_leaderboard(
|
pub async fn get_leaderboard(&self, _user_id: u32) -> Result<Vec<LeaderBoardTest>, sqlx::Error> {
|
||||||
&self,
|
|
||||||
_user_id: u32,
|
|
||||||
) -> Result<Vec<LeaderBoardTest>, sqlx::Error> {
|
|
||||||
let tests = sqlx::query!(
|
let tests = sqlx::query!(
|
||||||
"SELECT users.username, tests.wpm, tests.accuracy, tests.test_time, tests.test_length
|
"SELECT users.username, tests.wpm, tests.accuracy, tests.test_time, tests.test_length
|
||||||
FROM tests
|
FROM tests
|
||||||
|
@ -1,59 +1,40 @@
|
|||||||
use crate::api::sql::Database;
|
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use rocket::{
|
use rocket::serde::json::Json;
|
||||||
serde::{json::Json, Deserialize},
|
use rocket::http::Status;
|
||||||
State,
|
|
||||||
};
|
|
||||||
use std::{fs, vec};
|
use std::{fs, vec};
|
||||||
|
|
||||||
/// the datascructure that the webserver will recieve
|
|
||||||
/// when a post is made to the http://url/api/post_test route
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct PostTest<'r> {
|
|
||||||
pub test_type: &'r str,
|
|
||||||
pub test_length: u32,
|
|
||||||
pub test_time: u32,
|
|
||||||
pub test_seed: i64,
|
|
||||||
pub quote_id: i32,
|
|
||||||
pub wpm: u8,
|
|
||||||
pub accuracy: u8,
|
|
||||||
pub user_id: u32,
|
|
||||||
pub secret: &'r str,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Api Route that accepts test data and posts it to the database
|
|
||||||
/// Acessible from http://url/api/post_test
|
|
||||||
#[post("/post_test", data = "<test>")]
|
|
||||||
pub async fn create_test(test: Json<PostTest<'_>>, database: &State<Database>) {
|
|
||||||
let user_id = test.user_id;
|
|
||||||
match database.create_test(test).await {
|
|
||||||
Err(why) => {
|
|
||||||
println!("A database error occured creating a test, {why}");
|
|
||||||
}
|
|
||||||
Ok(()) => {
|
|
||||||
println!("Successfully created test for {user_id}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an array of words as Json
|
/// Returns an array of words as Json
|
||||||
/// Accessible from http://url/api/get_test
|
/// Accessible from http://url/api/get_test
|
||||||
#[get("/new_test")]
|
#[get("/test")]
|
||||||
pub fn new_test() -> Json<Vec<String>> {
|
pub fn generate_test() -> Result<Json<Vec<String>>, Status> {
|
||||||
let mut word_vec: Vec<&str> = vec![];
|
let mut word_vec: Vec<&str> = vec![];
|
||||||
let words: String = fs::read_to_string("wordlist.txt").unwrap();
|
|
||||||
for word in words.split('\n') {
|
// Read file
|
||||||
|
let words = match fs::read_to_string("wordlist.txt") {
|
||||||
|
Err(_) => {
|
||||||
|
println!(" >> wordlist.txt could not be found");
|
||||||
|
return Err(Status::NotFound)
|
||||||
|
},
|
||||||
|
Ok(words) => words,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populate word_vec
|
||||||
|
for word in words.lines() {
|
||||||
word_vec.push(word);
|
word_vec.push(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if word_vec is empty
|
||||||
|
if word_vec.is_empty() {
|
||||||
|
return Err(Status::InternalServerError);
|
||||||
|
}
|
||||||
|
|
||||||
let mut return_list: Vec<String> = vec![];
|
let mut return_list: Vec<String> = vec![];
|
||||||
|
|
||||||
let mut rng: ThreadRng = rand::thread_rng();
|
let mut rng: ThreadRng = rand::thread_rng();
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
let word = rng.gen_range(0..999);
|
let word_index = rng.gen_range(0..word_vec.len());
|
||||||
return_list.push(word_vec[word].to_string())
|
return_list.push(word_vec[word_index].to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
Json(return_list.clone())
|
Ok(Json(return_list))
|
||||||
}
|
}
|
||||||
|
148
src/api/user.rs
@ -1,148 +0,0 @@
|
|||||||
use rocket::{
|
|
||||||
http::Status,
|
|
||||||
serde::{json::Json, Deserialize, Serialize},
|
|
||||||
State,
|
|
||||||
};
|
|
||||||
|
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
|
||||||
|
|
||||||
use crate::api::sql::{Database, Test};
|
|
||||||
|
|
||||||
/// Struct representing the user
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct User<'r> {
|
|
||||||
username: &'r str,
|
|
||||||
password: &'r str,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Route takes data about the user as a struct
|
|
||||||
/// and then creates the user in the database
|
|
||||||
/// Acessible from http://url/api/create_user
|
|
||||||
#[post("/create_user", data = "<user>")]
|
|
||||||
pub async fn sign_up(user: Json<User<'_>>, database: &State<Database>) {
|
|
||||||
let secret: String = rand::thread_rng()
|
|
||||||
.sample_iter(&Alphanumeric)
|
|
||||||
.take(50)
|
|
||||||
.map(char::from)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
match database
|
|
||||||
.create_user(user.username, &sha256::digest(user.password), &secret)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Err(why) => {
|
|
||||||
println!("A database error occured during signup, {why}");
|
|
||||||
}
|
|
||||||
Ok(()) => {
|
|
||||||
println!("Succesfully Signed up User: {}", user.username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves tests associated with a specific user from the database and returns them as a JSON array.
|
|
||||||
///
|
|
||||||
/// # Endpoint
|
|
||||||
///
|
|
||||||
/// GET /api/get_tests/<user_id>/<secret>
|
|
||||||
///
|
|
||||||
/// # Path Parameters
|
|
||||||
///
|
|
||||||
/// - `user_id`: User ID of the user whose tests need to be retrieved.
|
|
||||||
/// - `secret`: Secret key for authentication.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// Returns a JSON array containing the user's tests if the user is authenticated and the tests are found.
|
|
||||||
///
|
|
||||||
/// If the user authentication fails, returns a `401 Unauthorized` status.
|
|
||||||
///
|
|
||||||
/// If the tests are not found or any database-related error occurs, returns a `404 Not Found` status.
|
|
||||||
///
|
|
||||||
/// # Example Request
|
|
||||||
///
|
|
||||||
/// ```bash
|
|
||||||
/// curl -X GET "https://example.com/api/get_tests/123/your_secret_key_here"
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Example Response
|
|
||||||
///
|
|
||||||
/// ```json
|
|
||||||
/// [
|
|
||||||
/// {
|
|
||||||
/// "test_type": "typing",
|
|
||||||
/// "test_length": 100,
|
|
||||||
/// "test_time": 300,
|
|
||||||
/// "test_seed": 987654321,
|
|
||||||
/// "quote_id": 123,
|
|
||||||
/// "wpm": 65,
|
|
||||||
/// "accuracy": 98
|
|
||||||
/// },
|
|
||||||
/// {
|
|
||||||
/// "test_type": "multiple_choice",
|
|
||||||
/// "test_length": 50,
|
|
||||||
/// "test_time": 150,
|
|
||||||
/// "test_seed": 123456789,
|
|
||||||
/// "quote_id": null,
|
|
||||||
/// "wpm": null,
|
|
||||||
/// "accuracy": 85
|
|
||||||
/// }
|
|
||||||
/// ]
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
#[get("/get_tests/<user_id>/<secret>")]
|
|
||||||
pub async fn get_tests(user_id: u32, secret: &str, database: &State<Database>) -> Result<Json<Vec<Test>>, Status> {
|
|
||||||
match database.authenticate_user(user_id, &secret).await {
|
|
||||||
Err(_) => return Err(Status::InternalServerError),
|
|
||||||
Ok(authenticated) => {
|
|
||||||
if !authenticated {
|
|
||||||
return Err(Status::Unauthorized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match database.get_user_tests(user_id, &secret).await {
|
|
||||||
Err(why) => {
|
|
||||||
println!("A database error occured during getting_tests, {why}");
|
|
||||||
Err(Status::NotFound)
|
|
||||||
}
|
|
||||||
Ok(tests) => {
|
|
||||||
println!("Succesfully Found Tests for User {user_id}");
|
|
||||||
Ok(Json(tests))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// takes the users login information and returns the users user id
|
|
||||||
/// which can be used to identify their tests etc.
|
|
||||||
/// Accessible from http://url/api/login
|
|
||||||
#[get("/login/<username>/<password>")]
|
|
||||||
pub async fn login(
|
|
||||||
username: &str,
|
|
||||||
password: &str,
|
|
||||||
database: &State<Database>,
|
|
||||||
) -> Json<Option<LoginResponse>> {
|
|
||||||
match database
|
|
||||||
.find_user(username, &sha256::digest(password))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Err(why) => {
|
|
||||||
println!("A database error occured during login for {username}, {why}");
|
|
||||||
Json(None)
|
|
||||||
}
|
|
||||||
Ok(user) => match user {
|
|
||||||
None => Json(None),
|
|
||||||
Some(user) => Json(Some(LoginResponse {
|
|
||||||
user_id: user.0,
|
|
||||||
secret: user.1,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct LoginResponse {
|
|
||||||
user_id: u32,
|
|
||||||
secret: String,
|
|
||||||
}
|
|
60
src/api/user/create.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use rocket::{
|
||||||
|
// http::Status,
|
||||||
|
serde::{json::Json, Deserialize},
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
use crate::api::sql::Database;
|
||||||
|
|
||||||
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
|
||||||
|
/// Struct representing the user.
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct User<'r> {
|
||||||
|
/// The username of the user.
|
||||||
|
username: &'r str,
|
||||||
|
/// The password of the user.
|
||||||
|
password: &'r str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new user in the database.
|
||||||
|
///
|
||||||
|
/// # Endpoint
|
||||||
|
///
|
||||||
|
/// POST `/api/user/create`
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// - `user`: Data about the user to be created.
|
||||||
|
/// - `database`: Instance of the database state.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Upon successful creation, returns a status indicating success (`200 OK`).
|
||||||
|
/// If a database error occurs, returns a `500 Internal Server Error`.
|
||||||
|
///
|
||||||
|
/// # Example Request
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
/// curl -X POST -H "Content-Type: application/json" -d '{"username":"example_user","password":"example_password"}' https://url/api/user/create
|
||||||
|
/// ```
|
||||||
|
#[post("/create", data = "<user>")]
|
||||||
|
pub async fn create(user: Json<User<'_>>, database: &State<Database>) {
|
||||||
|
let secret: String = rand::thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(50)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match database
|
||||||
|
.create_user(user.username, &sha256::digest(user.password), &secret)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(why) => {
|
||||||
|
println!("A database error occured during signup, {why}");
|
||||||
|
}
|
||||||
|
Ok(()) => {
|
||||||
|
println!("Succesfully Signed up User: {}", user.username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
src/api/user/login.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use rocket::{
|
||||||
|
http::Status,
|
||||||
|
serde::{json::Json, Serialize},
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
use crate::api::sql::Database;
|
||||||
|
|
||||||
|
/// Logs in the user and returns the user ID and secret.
|
||||||
|
///
|
||||||
|
/// # Endpoint
|
||||||
|
///
|
||||||
|
/// GET `/api/user/login/<username>/<password>`
|
||||||
|
///
|
||||||
|
/// # Path Parameters
|
||||||
|
///
|
||||||
|
/// - `username`: The username of the user.
|
||||||
|
/// - `password`: The password of the user.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns the user ID and secret upon successful login.
|
||||||
|
///
|
||||||
|
/// If the login credentials are invalid, returns a `401 Unauthorized` status.
|
||||||
|
///
|
||||||
|
/// If any database-related error occurs, returns a `500 Internal Server Error`.
|
||||||
|
///
|
||||||
|
/// # Example Request
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
/// curl -X GET "https://url/api/login/example_user/example_password"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example Response
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "user_id": "1234567890",
|
||||||
|
/// "secret": "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[get("/login/<username>/<password>")]
|
||||||
|
pub async fn login(username: &str, password: &str, database: &State<Database>) -> Result<Json<LoginResponse>, Status> {
|
||||||
|
match database
|
||||||
|
.find_user(username, &sha256::digest(password))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(why) => {
|
||||||
|
println!("A database error occured during login for {username}, {why}");
|
||||||
|
return Err(Status::InternalServerError);
|
||||||
|
}
|
||||||
|
Ok(user) => match user {
|
||||||
|
None => return Err(Status::Unauthorized),
|
||||||
|
Some(user) => Ok(Json(LoginResponse {
|
||||||
|
user_id: user.0,
|
||||||
|
secret: user.1,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct representing the response of login API.
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct LoginResponse {
|
||||||
|
/// The user ID of the logged-in user.
|
||||||
|
user_id: u32,
|
||||||
|
/// The secret key associated with the user.
|
||||||
|
secret: String,
|
||||||
|
}
|
4
src/api/user/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod login;
|
||||||
|
pub mod tests;
|
||||||
|
pub mod test;
|
||||||
|
pub mod create;
|
146
src/api/user/test.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use rocket::serde::{json::Json, Deserialize};
|
||||||
|
use rocket::State;
|
||||||
|
use rocket::http::Status;
|
||||||
|
use crate::api::sql::Database;
|
||||||
|
use crate::api::sql::Test;
|
||||||
|
|
||||||
|
/// the datascructure that the webserver will recieve
|
||||||
|
/// when a post is made to the http://url/api/post_test route
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct PostTest<'r> {
|
||||||
|
pub test_type: &'r str,
|
||||||
|
pub test_length: u32,
|
||||||
|
pub test_time: u32,
|
||||||
|
pub test_seed: i64,
|
||||||
|
pub quote_id: i32,
|
||||||
|
pub wpm: u8,
|
||||||
|
pub accuracy: u8,
|
||||||
|
pub user_id: u32,
|
||||||
|
pub secret: &'r str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a test a user has completed and adds it tho the database
|
||||||
|
///
|
||||||
|
/// # Endpoint
|
||||||
|
///
|
||||||
|
/// GET `/api/user/test`
|
||||||
|
///
|
||||||
|
/// # Path Parameters
|
||||||
|
///
|
||||||
|
/// `test_type`: Type of typing test the user has completed (CURRENTLY ONLY "WORDS")
|
||||||
|
/// `test_length`: Number of characters the user typed
|
||||||
|
/// `test_time`: Time in seconds that the test took to complet
|
||||||
|
/// `test_seed`: Seed used to generate the test (NOT CURRENTLY IN USE)
|
||||||
|
/// `quote_id`: Quote ID that the user typed (NOT CURRENTLY IN USE)
|
||||||
|
/// `wpm`: Number of words per minute the user typed (A WORD IS COUNTED AS 5 CHARACTERS)
|
||||||
|
/// `accuracy`: Percentage accuracy that the user achieved in the test
|
||||||
|
/// `user_id`: User ID of the user whose completed the test
|
||||||
|
/// `secret`: Secret key for authentication.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns the latest test of the user if the user is authenticated and the test is found.
|
||||||
|
///
|
||||||
|
/// `401 UNAUTHORIZED` - The user has failed to be authenticated
|
||||||
|
/// `500 INTERNAL SERVER ERROR` - The test could not be added to the database
|
||||||
|
///
|
||||||
|
/// # Example Request
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
/// curl -X GET "https://url/api/user/latest_user_test/123/your_secret_key_here"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example Response
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "test_type": "typing",
|
||||||
|
/// "test_length": 100,
|
||||||
|
/// "test_time": 300,
|
||||||
|
/// "test_seed": 987654321,
|
||||||
|
/// "quote_id": 123,
|
||||||
|
/// "wpm": 65,
|
||||||
|
/// "accuracy": 98
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[post("/test", data = "<test>")]
|
||||||
|
pub async fn post_test(test: Json<PostTest<'_>>, database: &State<Database>) -> Status {
|
||||||
|
match database.authenticate_user(test.user_id, test.secret).await {
|
||||||
|
Err(_) => return Status::InternalServerError,
|
||||||
|
Ok(authenticated) => {
|
||||||
|
if !authenticated { return Status::Unauthorized }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_id = test.user_id;
|
||||||
|
match database.create_test(test).await {
|
||||||
|
Err(why) => {
|
||||||
|
println!("A database error occured creating a test, {why}");
|
||||||
|
}
|
||||||
|
Ok(()) => {
|
||||||
|
println!("Successfully created test for {user_id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Status::Accepted
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the latest test associated with a specific user from the database and returns it as JSON.
|
||||||
|
///
|
||||||
|
/// # Endpoint
|
||||||
|
///
|
||||||
|
/// GET `/api/user/latest_test/<user_id>/<secret>`
|
||||||
|
///
|
||||||
|
/// # Path Parameters
|
||||||
|
///
|
||||||
|
/// - `user_id`: User ID of the user whose latest test needs to be retrieved.
|
||||||
|
/// - `secret`: Secret key for authentication.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns the latest test of the user if the user is authenticated and the test is found.
|
||||||
|
///
|
||||||
|
/// If the user authentication fails, returns a `401 Unauthorized` status.
|
||||||
|
///
|
||||||
|
/// If the test is not found or any database-related error occurs, returns a `404 Not Found` status.
|
||||||
|
///
|
||||||
|
/// # Example Request
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
/// curl -X GET "https://url/api/user/latest_user_test/123/your_secret_key_here"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example Response
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "test_type": "typing",
|
||||||
|
/// "test_length": 100,
|
||||||
|
/// "test_time": 300,
|
||||||
|
/// "test_seed": 987654321,
|
||||||
|
/// "quote_id": 123,
|
||||||
|
/// "wpm": 65,
|
||||||
|
/// "accuracy": 98
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[get("/test/<user_id>/<secret>")]
|
||||||
|
pub async fn get_latest_test(user_id: u32, secret: &str, database: &State<Database>) -> Result<Json<Test>, Status> {
|
||||||
|
match database.authenticate_user(user_id, &secret).await {
|
||||||
|
Err(_) => return Err(Status::InternalServerError),
|
||||||
|
Ok(authenticated) => {
|
||||||
|
if !authenticated { return Err(Status::Unauthorized) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match database.get_user_test(user_id).await {
|
||||||
|
Err(why) => {
|
||||||
|
println!(" >> A database error occured during getting_tests, {why}");
|
||||||
|
Err(Status::NotFound)
|
||||||
|
}
|
||||||
|
Ok(test) => {
|
||||||
|
println!(" >> Succesfully Found latestTest for User {user_id}");
|
||||||
|
Ok(Json(test))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
src/api/user/tests.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use rocket::{
|
||||||
|
http::Status,
|
||||||
|
serde::json::Json,
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
use crate::api::sql::{Database, Test};
|
||||||
|
|
||||||
|
/// Retrieves tests associated with a specific user from the database and returns them as a JSON array.
|
||||||
|
///
|
||||||
|
/// # Endpoint
|
||||||
|
///
|
||||||
|
/// GET /api/get_tests/<user_id>/<secret>
|
||||||
|
///
|
||||||
|
/// # Path Parameters
|
||||||
|
///
|
||||||
|
/// - `user_id`: User ID of the user whose tests need to be retrieved.
|
||||||
|
/// - `secret`: Secret key for authentication.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns a JSON array containing the user's tests if the user is authenticated and the tests are found.
|
||||||
|
///
|
||||||
|
/// If the user authentication fails, returns a `401 Unauthorized` status.
|
||||||
|
///
|
||||||
|
/// If the tests are not found or any database-related error occurs, returns a `404 Not Found` status.
|
||||||
|
///
|
||||||
|
/// # Example Request
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
/// curl -X GET "https://url/api/user/get_tests/123/your_secret_key_here"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example Response
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// [
|
||||||
|
/// {
|
||||||
|
/// "test_type": "typing",
|
||||||
|
/// "test_length": 100,
|
||||||
|
/// "test_time": 300,
|
||||||
|
/// "test_seed": 987654321,
|
||||||
|
/// "quote_id": 123,
|
||||||
|
/// "wpm": 65,
|
||||||
|
/// "accuracy": 98
|
||||||
|
/// },
|
||||||
|
/// {
|
||||||
|
/// "test_type": "multiple_choice",
|
||||||
|
/// "test_length": 50,
|
||||||
|
/// "test_time": 150,
|
||||||
|
/// "test_seed": 123456789,
|
||||||
|
/// "quote_id": null,
|
||||||
|
/// "wpm": null,
|
||||||
|
/// "accuracy": 85
|
||||||
|
/// }
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
#[get("/tests/<user_id>/<secret>")]
|
||||||
|
pub async fn tests(user_id: u32, secret: &str, database: &State<Database>) -> Result<Json<Vec<Test>>, Status> {
|
||||||
|
match database.authenticate_user(user_id, &secret).await {
|
||||||
|
Err(_) => return Err(Status::InternalServerError),
|
||||||
|
Ok(authenticated) => { if !authenticated { return Err(Status::Unauthorized) }}
|
||||||
|
}
|
||||||
|
|
||||||
|
match database.get_user_tests(user_id, &secret).await {
|
||||||
|
Err(why) => {
|
||||||
|
println!("A database error occured during getting_tests, {why}");
|
||||||
|
Err(Status::NotFound)
|
||||||
|
}
|
||||||
|
Ok(tests) => {
|
||||||
|
println!(" >> Succesfully Found {} Tests for User {user_id}", tests.len());
|
||||||
|
Ok(Json(tests))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/main.rs
@ -22,10 +22,20 @@ mod api;
|
|||||||
mod catchers;
|
mod catchers;
|
||||||
mod redirects;
|
mod redirects;
|
||||||
|
|
||||||
use crate::api::leaderboard::leaderboard;
|
use crate::api::{
|
||||||
use crate::api::sql::Database;
|
sql::Database,
|
||||||
use crate::api::test::{create_test, new_test};
|
|
||||||
use crate::api::user::{get_tests, login, sign_up};
|
user::{
|
||||||
|
create::create,
|
||||||
|
login::login,
|
||||||
|
tests::tests,
|
||||||
|
test::get_latest_test,
|
||||||
|
test::post_test,
|
||||||
|
},
|
||||||
|
test::generate_test,
|
||||||
|
leaderboard::leaderboard,
|
||||||
|
ping::ping,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::catchers::not_found::api_not_found;
|
use crate::catchers::not_found::api_not_found;
|
||||||
use crate::catchers::not_found::frontend_not_found;
|
use crate::catchers::not_found::frontend_not_found;
|
||||||
@ -33,16 +43,6 @@ use crate::catchers::not_found::documentation_not_found;
|
|||||||
use crate::redirects::leaderboard_redirect;
|
use crate::redirects::leaderboard_redirect;
|
||||||
use crate::redirects::typing_redirect;
|
use crate::redirects::typing_redirect;
|
||||||
|
|
||||||
|
|
||||||
// Imports for sql, see sql.rs for more information
|
|
||||||
|
|
||||||
/// Test api route that returns hello world.
|
|
||||||
/// Acessible from http://url/test
|
|
||||||
#[get("/")]
|
|
||||||
fn test() -> &'static str {
|
|
||||||
"Hello World! I'm A rocket Webserver"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main function which builds and launches the
|
/// The main function which builds and launches the
|
||||||
/// webserver with all appropriate routes and fileservers
|
/// webserver with all appropriate routes and fileservers
|
||||||
#[launch]
|
#[launch]
|
||||||
@ -53,19 +53,22 @@ async fn rocket() -> Rocket<Build> {
|
|||||||
|
|
||||||
// hosts the api routes necessary for the website
|
// hosts the api routes necessary for the website
|
||||||
// to interact with the database
|
// to interact with the database
|
||||||
.mount("/api/documentation", FileServer::from(relative!("documentation")))
|
.mount("/api/user", routes![
|
||||||
.register("/api/documentation", catchers![documentation_not_found])
|
create,
|
||||||
.mount("/api",routes![
|
|
||||||
sign_up,
|
|
||||||
login,
|
login,
|
||||||
|
tests,
|
||||||
new_test,
|
post_test,
|
||||||
create_test,
|
get_latest_test
|
||||||
get_tests,
|
])
|
||||||
leaderboard
|
.mount("/api", routes![
|
||||||
|
ping,
|
||||||
|
leaderboard,
|
||||||
|
generate_test
|
||||||
])
|
])
|
||||||
.register("/api", catchers![api_not_found])
|
.register("/api", catchers![api_not_found])
|
||||||
|
|
||||||
|
.mount("/api/documentation", FileServer::from(relative!("documentation/html")))
|
||||||
|
.register("/api/documentation", catchers![documentation_not_found])
|
||||||
|
|
||||||
// hosts the fileserver
|
// hosts the fileserver
|
||||||
.mount("/typing", FileServer::from(relative!("public")))
|
.mount("/typing", FileServer::from(relative!("public")))
|
||||||
@ -78,5 +81,5 @@ async fn rocket() -> Rocket<Build> {
|
|||||||
.mount("/leaderboard", routes![leaderboard_redirect])
|
.mount("/leaderboard", routes![leaderboard_redirect])
|
||||||
|
|
||||||
// testing only, should return "Hello world"
|
// testing only, should return "Hello world"
|
||||||
.mount("/test", routes![test])
|
|
||||||
}
|
}
|
||||||
|