Changed to separate page structure & api updates #1

Merged
Arlo merged 1 commits from feature/html-navbar into master 2024-03-25 10:00:47 +00:00
90 changed files with 1997 additions and 1645 deletions
Showing only changes of commit 0e2218d1c1 - Show all commits

View File

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

Binary file not shown.

View File

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

View File

@ -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.

View File

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

View File

@ -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
View 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
View 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",
...
]
```

View 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"
}
```

View 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
}
```

View File

@ -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">&lt;</span><span class="pl-ent">user_id</span><span class="pl-c1">&gt;</span>/<span class="pl-c1">&lt;</span><span class="pl-ent">secret</span><span class="pl-c1">&gt;</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>

View File

@ -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
} },
...
]
``` ```

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

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

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

View 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/&lt;username&gt;/&lt;password&gt;</code>
</h2><a id="user-content-get-apiuserloginusernamepassword" class="anchor" aria-label="Permalink: GET /api/user/login/&lt;username&gt;/&lt;password&gt;" 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>

View 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/&lt;user_id&gt;/&lt;secret&gt;</code></h2><a id="user-content-get-apiusertestuser_idsecret" class="anchor" aria-label="Permalink: GET /api/user/test/&lt;user_id&gt;/&lt;secret&gt;" 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>

View 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/&lt;user_id&gt;/&lt;secret&gt;</code>
</h2><a id="user-content-get-apiusertestsuser_idsecret" class="anchor" aria-label="Permalink: GET /api/user/tests/&lt;user_id&gt;/&lt;secret&gt;" 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>

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

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

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

View File

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

View File

@ -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`

View File

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

View File

@ -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">&lt;</span><span class="pl-ent">username</span><span class="pl-c1">&gt;</span>/<span class="pl-c1">&lt;</span><span class="pl-ent">password</span><span class="pl-c1">&gt;</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>

View File

@ -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"
}
```

View File

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

View File

@ -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"}'
``` ```

View 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"
}'
```

View File

@ -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);
}
} }
} }

View File

@ -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;
}

View File

@ -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();
}

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

View 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);

View 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 */
}

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

View 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;

View 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 */
}

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

View 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 */
}

View File

@ -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">

View 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);

View File

@ -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 */
}

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

Before

Width:  |  Height:  |  Size: 968 B

After

Width:  |  Height:  |  Size: 968 B

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 732 B

After

Width:  |  Height:  |  Size: 732 B

View File

Before

Width:  |  Height:  |  Size: 752 B

After

Width:  |  Height:  |  Size: 752 B

View File

@ -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() {

View File

@ -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
View 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;
}
}

View File

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

View 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();
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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
View 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"
}

View File

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

View File

@ -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))
} }

View File

@ -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
View 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
View 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
View 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
View 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
View 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))
}
}
}

View File

@ -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])
} }