new catchers for 404 errors
This commit is contained in:
parent
4a57128a27
commit
45e7887a7e
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.showUnlinkedFileNotification": false
|
||||||
|
}
|
26
documentation/not_found.html
Normal file
26
documentation/not_found.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<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>
|
||||||
|
<h1>The content you are looking for does not appear to be here...</h1>
|
||||||
|
<a href="/api/documentation/"><button>main page</button></a>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
public/Error/not_found.html
Normal file
12
public/Error/not_found.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Not Found</title>
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
The content you are looking for does not appear to be here...
|
||||||
|
<a href="/typing/"><button>main page</button></a>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,8 +1,5 @@
|
|||||||
use rocket::{ serde::json::Json, State };
|
use crate::api::sql::{Database, LeaderBoardTest};
|
||||||
use crate::typing::sql::{
|
use rocket::{serde::json::Json, State};
|
||||||
Database,
|
|
||||||
LeaderBoardTest
|
|
||||||
};
|
|
||||||
|
|
||||||
type LeaderBoardTests = Vec<LeaderBoardTest>;
|
type LeaderBoardTests = Vec<LeaderBoardTest>;
|
||||||
|
|
||||||
@ -10,15 +7,14 @@ type LeaderBoardTests = Vec<LeaderBoardTest>;
|
|||||||
/// a json array
|
/// a json array
|
||||||
/// Acessible from http://url/api/leaderboard
|
/// Acessible from http://url/api/leaderboard
|
||||||
#[get("/leaderboard")]
|
#[get("/leaderboard")]
|
||||||
pub async fn leaderboard( database: &State<Database> ) -> Option<Json<LeaderBoardTests>> {
|
pub async fn leaderboard(database: &State<Database>) -> Option<Json<LeaderBoardTests>> {
|
||||||
let leaderboard = match database.get_leaderboard(0).await {
|
let leaderboard = match database.get_leaderboard(0).await {
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
println!("Error getting leaderboard, {why}");
|
println!("Error getting leaderboard, {why}");
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
Ok(leaderboard) => { leaderboard }
|
Ok(leaderboard) => leaderboard,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Json(leaderboard))
|
Some(Json(leaderboard))
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
pub mod sql;
|
|
||||||
pub mod user;
|
|
||||||
pub mod leaderboard;
|
pub mod leaderboard;
|
||||||
pub mod test;
|
pub mod sql;
|
||||||
|
pub mod test;
|
||||||
|
pub mod user;
|
@ -4,16 +4,16 @@
|
|||||||
//! it abstracts away the rusqlite necessary to perform
|
//! it abstracts away the rusqlite necessary to perform
|
||||||
//! these functions
|
//! these functions
|
||||||
//! Author: Arlo Filley
|
//! Author: Arlo Filley
|
||||||
//!
|
//!
|
||||||
//! TODO:
|
//! TODO:
|
||||||
//! - put necessary structs into a different file
|
//! - put necessary structs into a different file
|
||||||
//! - create structure for the input of post test
|
//! - create structure for the input of post test
|
||||||
|
|
||||||
// Imports for json handling and rusqlite
|
// Imports for json handling and rusqlite
|
||||||
use sqlx::sqlite::{ SqlitePool, SqlitePoolOptions };
|
use rocket::serde::{json::Json, Serialize};
|
||||||
use rocket::serde::{ Serialize, json::Json };
|
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||||
|
|
||||||
use crate::typing::test::PostTest;
|
use crate::api::test::PostTest;
|
||||||
|
|
||||||
/// Contains the database connection pool
|
/// Contains the database connection pool
|
||||||
pub struct Database(SqlitePool);
|
pub struct Database(SqlitePool);
|
||||||
@ -24,7 +24,7 @@ impl Database {
|
|||||||
pub async fn new() -> Result<Self, sqlx::Error> {
|
pub async fn new() -> Result<Self, sqlx::Error> {
|
||||||
let pool = SqlitePoolOptions::new()
|
let pool = SqlitePoolOptions::new()
|
||||||
.max_connections(2)
|
.max_connections(2)
|
||||||
.connect("sqlite:/Users/arlo/code/cs_coursework/database/dev/database.sqlite")
|
.connect("sqlite:/Users/arlo/Code/Projects/cs_coursework/database/database.sqlite")
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Self(pool))
|
Ok(Self(pool))
|
||||||
@ -33,16 +33,20 @@ impl Database {
|
|||||||
/// Creates the necessary tables inside the database with
|
/// Creates the necessary tables inside the database with
|
||||||
/// correct normalised links between data for later querying
|
/// correct normalised links between data for later querying
|
||||||
pub async fn _new_database(&self) -> Result<(), sqlx::Error> {
|
pub async fn _new_database(&self) -> Result<(), sqlx::Error> {
|
||||||
sqlx::query!("
|
sqlx::query!(
|
||||||
|
"
|
||||||
CREATE TABLE IF NOT EXISTS Users (
|
CREATE TABLE IF NOT EXISTS Users (
|
||||||
user_id INTEGER PRIMARY KEY,
|
user_id INTEGER PRIMARY KEY,
|
||||||
username TEXT UNIQUE NOT NULL,
|
username TEXT UNIQUE NOT NULL,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
secret TEXT NOT NULL
|
secret TEXT NOT NULL
|
||||||
)"
|
)"
|
||||||
).execute(&self.0).await?;
|
)
|
||||||
|
.execute(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
sqlx::query!("
|
sqlx::query!(
|
||||||
|
"
|
||||||
CREATE TABLE IF NOT EXISTS Tests (
|
CREATE TABLE IF NOT EXISTS Tests (
|
||||||
test_id INTEGER PRIMARY KEY,
|
test_id INTEGER PRIMARY KEY,
|
||||||
test_type TEXT NOT NULL,
|
test_type TEXT NOT NULL,
|
||||||
@ -55,7 +59,9 @@ impl Database {
|
|||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
||||||
)"
|
)"
|
||||||
).execute(&self.0).await?;
|
)
|
||||||
|
.execute(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -64,15 +70,18 @@ impl Database {
|
|||||||
/// a database record with the data
|
/// a database record with the data
|
||||||
pub async fn create_test(&self, test: Json<PostTest<'_>>) -> Result<(), sqlx::Error> {
|
pub async fn create_test(&self, test: Json<PostTest<'_>>) -> Result<(), sqlx::Error> {
|
||||||
// Test to see whether the secret is correct
|
// Test to see whether the secret is correct
|
||||||
let user = sqlx::query!("
|
let user = sqlx::query!(
|
||||||
|
"
|
||||||
Select secret
|
Select secret
|
||||||
From Users
|
From Users
|
||||||
Where user_id=?",
|
Where user_id=?",
|
||||||
test.user_id
|
test.user_id
|
||||||
).fetch_one(&self.0).await?;
|
)
|
||||||
|
.fetch_one(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if user.secret != test.secret {
|
if user.secret != test.secret {
|
||||||
return Err(sqlx::Error::RowNotFound)
|
return Err(sqlx::Error::RowNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlx::query!("
|
sqlx::query!("
|
||||||
@ -86,25 +95,43 @@ impl Database {
|
|||||||
|
|
||||||
/// takes a username and password and creates a database
|
/// takes a username and password and creates a database
|
||||||
/// entry for a new user
|
/// entry for a new user
|
||||||
pub async fn create_user(&self, username: &str, password: &str, secret: &str) -> Result<(), sqlx::Error> {
|
pub async fn create_user(
|
||||||
sqlx::query!("
|
&self,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
secret: &str,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
INSERT INTO Users (username, password, secret)
|
INSERT INTO Users (username, password, secret)
|
||||||
VALUES (?1, ?2, ?3)",
|
VALUES (?1, ?2, ?3)",
|
||||||
username, password, secret
|
username,
|
||||||
).execute(&self.0).await?;
|
password,
|
||||||
|
secret
|
||||||
|
)
|
||||||
|
.execute(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// takes a username and password as inputs and returns the
|
/// takes a username and password as inputs and returns the
|
||||||
/// user_id and secret of the user if one exists
|
/// user_id and secret of the user if one exists
|
||||||
pub async fn find_user(&self, username: &str, password: &str) -> Result<Option<(u32, String)>, sqlx::Error> {
|
pub async fn find_user(
|
||||||
let user = sqlx::query!("
|
&self,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<Option<(u32, String)>, sqlx::Error> {
|
||||||
|
let user = sqlx::query!(
|
||||||
|
"
|
||||||
SELECT user_id, secret
|
SELECT user_id, secret
|
||||||
FROM Users
|
FROM Users
|
||||||
WHERE username=? AND password=?",
|
WHERE username=? AND password=?",
|
||||||
username, password
|
username,
|
||||||
).fetch_one(&self.0).await?;
|
password
|
||||||
|
)
|
||||||
|
.fetch_one(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let user_id = user.user_id.unwrap() as u32;
|
let user_id = user.user_id.unwrap() as u32;
|
||||||
let secret = user.secret.clone();
|
let secret = user.secret.clone();
|
||||||
@ -114,51 +141,111 @@ 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(&self, user_id: u32, secret: &str) -> Result<Vec<Test>, sqlx::Error> {
|
pub async fn get_user_tests(
|
||||||
let tests = sqlx::query!("
|
&self,
|
||||||
|
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, secret
|
user_id,
|
||||||
).fetch_all(&self.0).await?;
|
secret
|
||||||
|
)
|
||||||
|
.fetch_all(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
println!("{}", tests.len());
|
println!("{}", tests.len());
|
||||||
|
|
||||||
let user_tests = tests.iter()
|
let user_tests = tests
|
||||||
.map(|test| Test {
|
.iter()
|
||||||
test_type: test.test_type.clone(),
|
.map(|test| Test {
|
||||||
test_length: test.test_length.unwrap() as u32,
|
test_type: test.test_type.clone(),
|
||||||
test_time: test.test_time.unwrap() as u32,
|
test_length: test.test_length.unwrap() as u32,
|
||||||
test_seed: test.test_seed.unwrap(),
|
test_time: test.test_time.unwrap() as u32,
|
||||||
quote_id: test.quote_id.unwrap() as i32,
|
test_seed: test.test_seed.unwrap(),
|
||||||
wpm: test.wpm.unwrap() as u8,
|
quote_id: test.quote_id.unwrap() as i32,
|
||||||
accuracy: test.accuracy.unwrap() as u8
|
wpm: test.wpm.unwrap() as u8,
|
||||||
|
accuracy: test.accuracy.unwrap() as u8,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(user_tests)
|
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(&self, _user_id: u32) -> Result<Vec<LeaderBoardTest>, sqlx::Error> {
|
pub async fn get_leaderboard(
|
||||||
|
&self,
|
||||||
|
_user_id: u32,
|
||||||
|
) -> Result<Vec<LeaderBoardTest>, sqlx::Error> {
|
||||||
let tests = sqlx::query!(
|
let tests = sqlx::query!(
|
||||||
"SELECT users.username, tests.wpm
|
"SELECT users.username, tests.wpm
|
||||||
FROM tests
|
FROM tests
|
||||||
INNER JOIN users ON users.user_id = tests.user_id
|
INNER JOIN users ON users.user_id = tests.user_id
|
||||||
GROUP BY users.username
|
GROUP BY users.username
|
||||||
ORDER BY tests.wpm DESC",
|
ORDER BY tests.wpm DESC",
|
||||||
).fetch_all(&self.0).await?;
|
)
|
||||||
|
.fetch_all(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let leaderboard_tests = tests.iter()
|
let leaderboard_tests = tests
|
||||||
.map(|test| LeaderBoardTest {
|
.iter()
|
||||||
username: test.username.clone(),
|
.map(|test| LeaderBoardTest {
|
||||||
wpm: test.wpm.unwrap() as u8
|
username: test.username.clone(),
|
||||||
|
wpm: test.wpm.unwrap() as u8,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(leaderboard_tests)
|
Ok(leaderboard_tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Authenticates a user based on their user ID and secret.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_id` - The ID of the user to authenticate.
|
||||||
|
/// * `secret` - The secret associated with the user.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns a `Result` indicating whether the authentication was successful or not.
|
||||||
|
/// - `Ok(true)` if authentication is successful.
|
||||||
|
/// - `Ok(false)` if authentication fails.
|
||||||
|
/// - `Err` if there's an error accessing the database.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use crate::sql::Database;
|
||||||
|
///
|
||||||
|
/// #[tokio::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let db = Database::new().await.expect("Failed to create database connection");
|
||||||
|
///
|
||||||
|
/// // Authenticate user with user ID 123 and secret "example_secret"
|
||||||
|
/// let authenticated = db.authenticate_user(123, "example_secret").await;
|
||||||
|
/// assert_eq!(authenticated, Ok(true));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn authenticate_user(&self, user_id: u32, secret: &str) -> Result<bool, sqlx::Error> {
|
||||||
|
// Test to see whether the secret is correct
|
||||||
|
let user = sqlx::query!(
|
||||||
|
"
|
||||||
|
Select secret
|
||||||
|
From Users
|
||||||
|
Where user_id=?",
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_one(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Compare the fetched secret with the provided one
|
||||||
|
Ok(user.secret == secret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// struct representing data that needs to be sent
|
/// struct representing data that needs to be sent
|
||||||
@ -182,4 +269,4 @@ pub struct Test {
|
|||||||
pub struct LeaderBoardTest {
|
pub struct LeaderBoardTest {
|
||||||
username: String,
|
username: String,
|
||||||
wpm: u8,
|
wpm: u8,
|
||||||
}
|
}
|
@ -1,16 +1,10 @@
|
|||||||
use crate::typing::sql::Database;
|
use crate::api::sql::Database;
|
||||||
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
serde::{ Deserialize, json::Json },
|
serde::{json::Json, Deserialize},
|
||||||
State
|
State,
|
||||||
};
|
|
||||||
use rand::{
|
|
||||||
Rng,
|
|
||||||
rngs::ThreadRng
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
vec,
|
|
||||||
};
|
};
|
||||||
|
use std::{fs, vec};
|
||||||
|
|
||||||
/// the datascructure that the webserver will recieve
|
/// the datascructure that the webserver will recieve
|
||||||
/// when a post is made to the http://url/api/post_test route
|
/// when a post is made to the http://url/api/post_test route
|
||||||
@ -25,7 +19,7 @@ pub struct PostTest<'r> {
|
|||||||
pub wpm: u8,
|
pub wpm: u8,
|
||||||
pub accuracy: u8,
|
pub accuracy: u8,
|
||||||
pub user_id: u32,
|
pub user_id: u32,
|
||||||
pub secret: &'r str
|
pub secret: &'r str,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Api Route that accepts test data and posts it to the database
|
/// Api Route that accepts test data and posts it to the database
|
||||||
@ -34,8 +28,12 @@ pub struct PostTest<'r> {
|
|||||||
pub async fn create_test(test: Json<PostTest<'_>>, database: &State<Database>) {
|
pub async fn create_test(test: Json<PostTest<'_>>, database: &State<Database>) {
|
||||||
let user_id = test.user_id;
|
let user_id = test.user_id;
|
||||||
match database.create_test(test).await {
|
match database.create_test(test).await {
|
||||||
Err(why) => { println!("A database error occured creating a test, {why}"); }
|
Err(why) => {
|
||||||
Ok(()) => { println!("Successfully created test for {user_id}"); }
|
println!("A database error occured creating a test, {why}");
|
||||||
|
}
|
||||||
|
Ok(()) => {
|
||||||
|
println!("Successfully created test for {user_id}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +41,7 @@ pub async fn create_test(test: Json<PostTest<'_>>, database: &State<Database>) {
|
|||||||
/// Accessible from http://url/api/get_test
|
/// Accessible from http://url/api/get_test
|
||||||
#[get("/new_test")]
|
#[get("/new_test")]
|
||||||
pub fn new_test() -> Json<Vec<String>> {
|
pub fn new_test() -> Json<Vec<String>> {
|
||||||
let mut word_vec: Vec<&str> = vec![];
|
let mut word_vec: Vec<&str> = vec![];
|
||||||
let words: String = fs::read_to_string("wordlist.txt").unwrap();
|
let words: String = fs::read_to_string("wordlist.txt").unwrap();
|
||||||
for word in words.split('\n') {
|
for word in words.split('\n') {
|
||||||
word_vec.push(word);
|
word_vec.push(word);
|
||||||
@ -58,4 +56,4 @@ pub fn new_test() -> Json<Vec<String>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Json(return_list.clone())
|
Json(return_list.clone())
|
||||||
}
|
}
|
148
src/api/user.rs
Normal file
148
src/api/user.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
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,
|
||||||
|
}
|
1
src/catchers/mod.rs
Normal file
1
src/catchers/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod not_found;
|
16
src/catchers/not_found.rs
Normal file
16
src/catchers/not_found.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use rocket::{response::content::RawHtml, Request};
|
||||||
|
|
||||||
|
#[catch(404)]
|
||||||
|
pub fn api_not_found(req: &Request) -> String {
|
||||||
|
format!("Sorry, '{}' couldn't be found.", req.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[catch(404)]
|
||||||
|
pub fn frontend_not_found<'a>(_req: &Request) -> RawHtml<&'a str> {
|
||||||
|
RawHtml(include_str!("../../public/Error/not_found.html"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[catch(404)]
|
||||||
|
pub fn documentation_not_found<'a>(_req: &Request) -> RawHtml<&'a str> {
|
||||||
|
RawHtml(include_str!("../../documentation/not_found.html"))
|
||||||
|
}
|
23
src/main.rs
23
src/main.rs
@ -40,12 +40,17 @@ use rocket::{
|
|||||||
Build, Rocket,
|
Build, Rocket,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod typing;
|
mod api;
|
||||||
|
mod catchers;
|
||||||
|
|
||||||
use crate::typing::leaderboard::leaderboard;
|
use crate::api::leaderboard::leaderboard;
|
||||||
use crate::typing::sql::Database;
|
use crate::api::sql::Database;
|
||||||
use crate::typing::test::{create_test, new_test};
|
use crate::api::test::{create_test, new_test};
|
||||||
use crate::typing::user::{get_tests, login, sign_up};
|
use crate::api::user::{get_tests, login, sign_up};
|
||||||
|
|
||||||
|
use catchers::not_found::api_not_found;
|
||||||
|
use catchers::not_found::frontend_not_found;
|
||||||
|
use catchers::not_found::documentation_not_found;
|
||||||
|
|
||||||
// Imports for sql, see sql.rs for more information
|
// Imports for sql, see sql.rs for more information
|
||||||
|
|
||||||
@ -66,6 +71,8 @@ async fn rocket() -> Rocket<Build> {
|
|||||||
.mount("/test", routes![test])
|
.mount("/test", routes![test])
|
||||||
// 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")))
|
||||||
|
.register("/api/documentation", catchers![documentation_not_found])
|
||||||
.mount(
|
.mount(
|
||||||
"/api",
|
"/api",
|
||||||
routes![
|
routes![
|
||||||
@ -77,7 +84,11 @@ async fn rocket() -> Rocket<Build> {
|
|||||||
new_test,
|
new_test,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
.register("/api", catchers![api_not_found])
|
||||||
|
|
||||||
|
|
||||||
// hosts the fileserver
|
// hosts the fileserver
|
||||||
.mount("/typing", FileServer::from(relative!("websites/Typing")))
|
.mount("/typing", FileServer::from(relative!("public")))
|
||||||
|
.register("/typing", catchers![frontend_not_found])
|
||||||
.manage(Database::new().await.unwrap())
|
.manage(Database::new().await.unwrap())
|
||||||
}
|
}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
use rocket::{
|
|
||||||
serde::{Deserialize, json::Json, Serialize},
|
|
||||||
State
|
|
||||||
};
|
|
||||||
|
|
||||||
use rand::{ distributions::Alphanumeric, Rng };
|
|
||||||
|
|
||||||
use crate::typing::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); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the users tests from the database and returns it as a
|
|
||||||
/// json array.
|
|
||||||
/// Accessible from http://url/api/get_user_tests
|
|
||||||
#[get("/get_tests/<user_id>/<secret>")]
|
|
||||||
pub async fn get_tests(user_id: u32, secret: String, database: &State<Database>) -> Option<Json<Vec<Test>>> {
|
|
||||||
match database.get_user_tests(user_id, &secret).await {
|
|
||||||
Err(why) => {
|
|
||||||
println!("A database error occured during getting_tests, {why}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Ok(tests) => {
|
|
||||||
println!("Succesfully Found Tests for User {user_id}");
|
|
||||||
Some(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,
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user