migrated database to sqlite
This commit is contained in:
parent
2ae7f236ce
commit
b3ed15811e
12
.gitignore
vendored
12
.gitignore
vendored
@ -1,13 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
/database/database.sqlite
|
|
||||||
/NEA_Screenshots
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.gitignore
|
.env
|
||||||
/.database
|
|
||||||
start.sh
|
|
||||||
TODO.md
|
|
||||||
/server
|
|
||||||
/servers_old
|
|
||||||
Rocket.toml
|
|
||||||
/.hidden
|
|
@ -4,9 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rusqlite = "0.28.0"
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
sha256 = "1.1.1"
|
sha256 = "1.1.1"
|
||||||
|
sqlx = { version = "0.7.1", features = ["macros", "sqlite", "runtime-tokio"] }
|
||||||
|
|
||||||
[dependencies.rocket]
|
[dependencies.rocket]
|
||||||
version = "0.5.0-rc.2"
|
version = "0.5.0-rc.2"
|
||||||
|
BIN
database/dev/database.sqlite
Executable file
BIN
database/dev/database.sqlite
Executable file
Binary file not shown.
20
src/main.rs
20
src/main.rs
@ -45,7 +45,7 @@ mod typing;
|
|||||||
|
|
||||||
use crate::servers::server::{server, server_info};
|
use crate::servers::server::{server, server_info};
|
||||||
use crate::typing::leaderboard::leaderboard;
|
use crate::typing::leaderboard::leaderboard;
|
||||||
use crate::typing::sql::*;
|
use crate::typing::sql::Database;
|
||||||
use crate::typing::test::{create_test, new_test};
|
use crate::typing::test::{create_test, new_test};
|
||||||
use crate::typing::user::{get_tests, login, sign_up};
|
use crate::typing::user::{get_tests, login, sign_up};
|
||||||
|
|
||||||
@ -54,14 +54,14 @@ use crate::typing::user::{get_tests, login, sign_up};
|
|||||||
/// Test api route that returns hello world.
|
/// Test api route that returns hello world.
|
||||||
/// Acessible from http://url/test
|
/// Acessible from http://url/test
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn test() -> String {
|
fn test() -> &'static str {
|
||||||
String::from("Hello World!")
|
"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]
|
||||||
fn rocket() -> Rocket<Build> {
|
async fn rocket() -> Rocket<Build> {
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.attach(CORS)
|
.attach(CORS)
|
||||||
// testing only, should return "Hello world"
|
// testing only, should return "Hello world"
|
||||||
@ -71,7 +71,6 @@ fn rocket() -> Rocket<Build> {
|
|||||||
.mount(
|
.mount(
|
||||||
"/api",
|
"/api",
|
||||||
routes![
|
routes![
|
||||||
create_database,
|
|
||||||
sign_up,
|
sign_up,
|
||||||
create_test,
|
create_test,
|
||||||
login,
|
login,
|
||||||
@ -83,9 +82,10 @@ fn rocket() -> Rocket<Build> {
|
|||||||
.mount("/api", routes![server, server_info])
|
.mount("/api", routes![server, server_info])
|
||||||
// hosts the fileserver
|
// hosts the fileserver
|
||||||
.mount("/typing", FileServer::from(relative!("websites/Typing")))
|
.mount("/typing", FileServer::from(relative!("websites/Typing")))
|
||||||
.mount("/servers", FileServer::from(relative!("websites/Servers")))
|
.manage(Database::new().await.unwrap())
|
||||||
.mount(
|
// .mount("/servers", FileServer::from(relative!("websites/Servers")))
|
||||||
"/BitBurner",
|
//.mount(
|
||||||
FileServer::from(relative!("websites/BitBurner")),
|
// "/BitBurner",
|
||||||
)
|
// FileServer::from(relative!("websites/BitBurner")),
|
||||||
|
//)
|
||||||
}
|
}
|
||||||
|
@ -10,176 +10,138 @@
|
|||||||
//! - 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 rusqlite::{Connection, Result};
|
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||||
use rocket::serde::Serialize;
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
|
pub struct Database(SqlitePool);
|
||||||
|
|
||||||
/// gets a connection to the database and returns it as
|
/// gets a connection to the database and returns it as
|
||||||
/// a rusqlite::connection
|
/// a rusqlite::connection
|
||||||
fn get_connection() -> rusqlite::Connection {
|
impl Database {
|
||||||
Connection::open("database/database.sqlite")
|
pub async fn new() -> Result<Self, sqlx::Error> {
|
||||||
.expect("Error creating database connection")
|
let pool = SqlitePoolOptions::new()
|
||||||
}
|
.max_connections(2)
|
||||||
|
.connect("sqlite:/Users/arlo/code/cs_coursework/database/dev/database.sqlite")
|
||||||
|
.await?;
|
||||||
|
|
||||||
/// Creates the necessary tables inside the database with
|
Ok(Self(pool))
|
||||||
/// correct normalised links between data for later
|
|
||||||
/// querying
|
|
||||||
fn new_database() -> Result<()> {
|
|
||||||
let connection = get_connection();
|
|
||||||
|
|
||||||
connection.execute(
|
|
||||||
"CREATE TABLE IF NOT EXISTS users (
|
|
||||||
user_id INTEGER PRIMARY KEY,
|
|
||||||
username TEXT UNIQUE NOT NULL,
|
|
||||||
password TEXT NOT NULL
|
|
||||||
)",
|
|
||||||
()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
connection.execute(
|
|
||||||
"CREATE TABLE IF NOT EXISTS tests (
|
|
||||||
test_id INTEGER PRIMARY KEY,
|
|
||||||
test_type TEXT NOT NULL,
|
|
||||||
test_length INTEGER,
|
|
||||||
test_time INTEGER,
|
|
||||||
test_seed INTEGER,
|
|
||||||
quote_id INTEGER,
|
|
||||||
wpm INTEGER,
|
|
||||||
accuracy INTEGER,
|
|
||||||
user_id INTEGER,
|
|
||||||
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
|
||||||
)",
|
|
||||||
()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Api route that creates a database if one
|
|
||||||
/// does not already exist.
|
|
||||||
/// Acessible from http://url/api/create_database
|
|
||||||
#[get("/create_database")]
|
|
||||||
pub fn create_database() -> String {
|
|
||||||
let database = new_database();
|
|
||||||
match database {
|
|
||||||
Err(why) => format!("Error: {why}"),
|
|
||||||
Ok(_) => format!("Sucessfully created the database")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// takes necessary data about a test and creates
|
/// Creates the necessary tables inside the database with
|
||||||
/// a database record with the data
|
/// correct normalised links between data for later querying
|
||||||
pub fn post_test(
|
pub async fn new_database(&self) -> Result<(), sqlx::Error> {
|
||||||
test_type: &str,
|
sqlx::query!("
|
||||||
test_length: u32,
|
CREATE TABLE IF NOT EXISTS Users (
|
||||||
test_time: u32,
|
user_id INTEGER PRIMARY KEY,
|
||||||
test_seed: i64,
|
username TEXT UNIQUE NOT NULL,
|
||||||
quote_id: i32,
|
password TEXT NOT NULL,
|
||||||
wpm: u8,
|
secret TEXT NOT NULL
|
||||||
accuracy: u8,
|
)"
|
||||||
user_id: u32
|
).execute(&self.0).await?;
|
||||||
) -> Result<()> {
|
|
||||||
let connection = get_connection();
|
|
||||||
|
|
||||||
connection.execute(
|
sqlx::query!("
|
||||||
"INSERT INTO tests (
|
CREATE TABLE IF NOT EXISTS Tests (
|
||||||
test_type,
|
test_id INTEGER PRIMARY KEY,
|
||||||
test_length,
|
test_type TEXT NOT NULL,
|
||||||
test_time,
|
test_length INTEGER,
|
||||||
test_seed,
|
test_time INTEGER,
|
||||||
quote_id,
|
test_seed INTEGER,
|
||||||
wpm,
|
quote_id INTEGER,
|
||||||
accuracy,
|
wpm INTEGER,
|
||||||
user_id
|
accuracy INTEGER,
|
||||||
)
|
user_id INTEGER,
|
||||||
VALUES(
|
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
||||||
?1,
|
)"
|
||||||
?2,
|
).execute(&self.0).await?;
|
||||||
?3,
|
|
||||||
?4,
|
|
||||||
?5,
|
|
||||||
?6,
|
|
||||||
?7,
|
|
||||||
?8
|
|
||||||
)
|
|
||||||
",
|
|
||||||
(
|
|
||||||
test_type,
|
|
||||||
test_length,
|
|
||||||
test_time,
|
|
||||||
test_seed,
|
|
||||||
quote_id,
|
|
||||||
wpm,
|
|
||||||
accuracy,
|
|
||||||
user_id
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// takes a username and password and creates a database
|
/// takes necessary data about a test and creates
|
||||||
/// entry for a new user
|
/// a database record with the data
|
||||||
pub fn create_user(
|
pub async fn create_test(&self, test_type: &str, test_length: u32, test_time: u32, test_seed: i64, quote_id: i32, wpm: u8, accuracy: u8, user_id: u32) -> Result<(), sqlx::Error> {
|
||||||
username: &str,
|
sqlx::query!("
|
||||||
password: &str
|
INSERT INTO Tests (test_type, test_length, test_time, test_seed, quote_id, wpm, accuracy, user_id)
|
||||||
) -> Result<()> {
|
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||||
let connection = get_connection();
|
test_type, test_length, test_time, test_seed, quote_id, wpm, accuracy, user_id
|
||||||
|
).execute(&self.0).await?;
|
||||||
|
|
||||||
connection.execute(
|
Ok(())
|
||||||
"
|
}
|
||||||
INSERT INTO users (
|
|
||||||
username,
|
|
||||||
password
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
?1,
|
|
||||||
?2
|
|
||||||
)
|
|
||||||
",
|
|
||||||
(
|
|
||||||
username,
|
|
||||||
password
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
/// takes a username and password and creates a database
|
||||||
}
|
/// entry for a new user
|
||||||
|
pub async fn create_user(&self, username: &str, password: &str) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!("
|
||||||
|
INSERT INTO Users (username, password)
|
||||||
|
VALUES (?1, ?2)",
|
||||||
|
username, password
|
||||||
|
).execute(&self.0).await?;
|
||||||
|
|
||||||
/// struct which can be deserialised
|
Ok(())
|
||||||
/// from json to get the user_id
|
}
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct User {
|
|
||||||
user_id: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// takes a username and password as inputs and returns the
|
/// takes a username and password as inputs and returns the
|
||||||
/// user_id of the user if one exists
|
/// user_id and secret of the user if one exists
|
||||||
pub fn find_user(
|
pub async fn find_user(&self, username: &str, password: &str) -> Result<Option<(u32, String)>, sqlx::Error> {
|
||||||
username: &str,
|
let user = sqlx::query!("
|
||||||
password: &str
|
SELECT user_id, secret
|
||||||
) -> Result<u32> {
|
FROM Users
|
||||||
let mut user_id: u32 = 0;
|
WHERE username=:username AND password=:password",
|
||||||
let connection = get_connection();
|
username, password
|
||||||
let mut statement = connection.prepare(
|
).fetch_all(&self.0).await?;
|
||||||
"SELECT user_id
|
|
||||||
FROM users
|
|
||||||
WHERE username=:username AND password=:password",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let iter = statement
|
let user_id = user[0].user_id.unwrap() as u32;
|
||||||
.query_map(
|
let secret = user[0].secret.clone();
|
||||||
&[(":username", username), (":password", password)], |row| {
|
|
||||||
Ok( User {
|
Ok(Some((user_id, secret)))
|
||||||
user_id: row.get(0)?
|
}
|
||||||
|
|
||||||
|
/// returns all the tests that a given user_id has
|
||||||
|
/// completed from the database
|
||||||
|
pub async fn get_user_tests(&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
|
||||||
|
FROM tests
|
||||||
|
INNER JOIN users ON users.user_id = tests.user_id
|
||||||
|
WHERE users.user_id=:user_id AND users.secret=:secret",
|
||||||
|
user_id, secret
|
||||||
|
).fetch_all(&self.0).await?;
|
||||||
|
|
||||||
|
let user_tests = tests.iter()
|
||||||
|
.map(|test| 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
|
||||||
})
|
})
|
||||||
}
|
.collect();
|
||||||
)?;
|
|
||||||
|
|
||||||
for i in iter {
|
Ok(user_tests)
|
||||||
user_id = i.unwrap().user_id;
|
|
||||||
}
|
}
|
||||||
|
/// returns a vector of leaderboard tests, where each one is the fastest words
|
||||||
|
/// per minute that a given user has achieved
|
||||||
|
pub async fn get_leaderboard(&self, _user_id: u32) -> Result<Vec<LeaderBoardTest>, sqlx::Error> {
|
||||||
|
let tests = sqlx::query!(
|
||||||
|
"SELECT users.username, tests.wpm
|
||||||
|
FROM tests
|
||||||
|
INNER JOIN users ON users.user_id = tests.user_id
|
||||||
|
GROUP BY users.username
|
||||||
|
ORDER BY tests.wpm DESC",
|
||||||
|
).fetch_all(&self.0).await?;
|
||||||
|
|
||||||
Ok(user_id)
|
let leaderboard_tests = tests.iter()
|
||||||
|
.map(|test| LeaderBoardTest {
|
||||||
|
username: test.username.clone(),
|
||||||
|
wpm: test.wpm.unwrap() as u8
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(leaderboard_tests)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// struct representing data that needs to be sent
|
/// struct representing data that needs to be sent
|
||||||
@ -196,39 +158,6 @@ pub struct Test {
|
|||||||
accuracy: u8,
|
accuracy: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns all the tests that a given user_id has
|
|
||||||
/// completed from the database
|
|
||||||
pub fn get_user_tests(
|
|
||||||
user_id: u32
|
|
||||||
) -> Result<Vec<Test>> {
|
|
||||||
let connection = get_connection();
|
|
||||||
let mut statement = connection.prepare(
|
|
||||||
"SELECT test_type, test_length, test_time, test_seed, quote_id, wpm, accuracy
|
|
||||||
FROM tests
|
|
||||||
WHERE user_id=:user_id",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let test_iter = statement
|
|
||||||
.query_map(&[(":user_id", &user_id.to_string())], |row| {
|
|
||||||
Ok( Test {
|
|
||||||
test_type: row.get(0)?,
|
|
||||||
test_length: row.get(1)?,
|
|
||||||
test_time: row.get(2)?,
|
|
||||||
test_seed: row.get(3)?,
|
|
||||||
quote_id: row.get(4)?,
|
|
||||||
wpm: row.get(5)?,
|
|
||||||
accuracy: row.get(6)?
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut tests: Vec<Test> = vec![];
|
|
||||||
for test in test_iter {
|
|
||||||
tests.push(test.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tests)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// struct that represents all the data that gets sent to the user
|
/// struct that represents all the data that gets sent to the user
|
||||||
/// when they make a leaderboard request
|
/// when they make a leaderboard request
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -238,32 +167,3 @@ pub struct LeaderBoardTest {
|
|||||||
wpm: u8,
|
wpm: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns a vector of leaderboard tests, where each one is the fastest words
|
|
||||||
/// per minute that a given user has achieved
|
|
||||||
pub fn get_leaderboard(
|
|
||||||
_user_id: u32
|
|
||||||
) -> Result<Vec<LeaderBoardTest>>{
|
|
||||||
let connection = get_connection();
|
|
||||||
let mut statement = connection.prepare(
|
|
||||||
"SELECT users.username, MAX(tests.wpm)
|
|
||||||
FROM tests
|
|
||||||
INNER JOIN users ON users.user_id = tests.user_id
|
|
||||||
GROUP BY users.username
|
|
||||||
ORDER BY tests.wpm DESC",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let test_iter = statement
|
|
||||||
.query_map((), |row| {
|
|
||||||
Ok( LeaderBoardTest {
|
|
||||||
username: row.get(0)?,
|
|
||||||
wpm: row.get(1)?
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut tests: Vec<LeaderBoardTest> = vec![];
|
|
||||||
for test in test_iter {
|
|
||||||
tests.push(test.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tests)
|
|
||||||
}
|
|
@ -1,15 +1,9 @@
|
|||||||
use rocket::serde::{
|
use rocket::{
|
||||||
Deserialize,
|
serde::{Deserialize, json::Json, Serialize},
|
||||||
json::Json
|
State
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::typing::sql::{
|
use crate::typing::sql::{ Database, Test };
|
||||||
get_user_tests,
|
|
||||||
find_user,
|
|
||||||
create_user,
|
|
||||||
|
|
||||||
Test,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Struct representing the user
|
/// Struct representing the user
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -23,28 +17,56 @@ pub struct User<'r> {
|
|||||||
/// and then creates the user in the database
|
/// and then creates the user in the database
|
||||||
/// Acessible from http://url/api/create_user
|
/// Acessible from http://url/api/create_user
|
||||||
#[post("/create_user", data = "<user>")]
|
#[post("/create_user", data = "<user>")]
|
||||||
pub fn sign_up(user: Json<User<'_>>) {
|
pub async fn sign_up(user: Json<User<'_>>, database: &State<Database>) {
|
||||||
create_user(
|
match database.create_user( user.username, &sha256::digest(user.password) ).await {
|
||||||
user.username,
|
Err(why) => {
|
||||||
&sha256::digest(user.password),
|
println!("A database error occured during signup, {}", why);
|
||||||
).expect("Error: Couldn't create new user");
|
}
|
||||||
|
Ok(()) => {
|
||||||
|
println!("Succesfully Signed up user {}", user.username);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the users tests from the database and returns it as a
|
/// Gets the users tests from the database and returns it as a
|
||||||
/// json array.
|
/// json array.
|
||||||
/// Accessible from http://url/api/get_user_tests
|
/// Accessible from http://url/api/get_user_tests
|
||||||
#[get("/get_tests/<user_id>")]
|
#[get("/get_tests/<user_id>/<secret>")]
|
||||||
pub fn get_tests(user_id: u32) -> Json<Vec<Test>> {
|
pub async fn get_tests(user_id: u32, secret: String, database: &State<Database>) -> Option<Json<Vec<Test>>> {
|
||||||
let tests: Vec<Test> = get_user_tests(user_id).expect("error finding user_id");
|
match database.get_user_tests(user_id, &secret).await {
|
||||||
Json(tests)
|
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
|
/// takes the users login information and returns the users user id
|
||||||
/// which can be used to identify their tests etc.
|
/// which can be used to identify their tests etc.
|
||||||
/// Accessible from http://url/api/login
|
/// Accessible from http://url/api/login
|
||||||
#[get("/login/<username>/<password>")]
|
#[get("/login/<username>/<password>")]
|
||||||
pub fn login(username: &str, password: &str) -> String {
|
pub async fn login(username: &str, password: &str, database: &State<Database>) -> Option<Json<LoginResponse>> {
|
||||||
let user_id = find_user(username, &sha256::digest(password)).expect("error finding user_id");
|
match database.find_user(username, &sha256::digest(password)).await {
|
||||||
user_id.to_string()
|
Err(why) => {
|
||||||
|
println!("A database error occured during login for {username}, {why}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Ok(user) => {
|
||||||
|
match user {
|
||||||
|
None => None,
|
||||||
|
Some(user) => { Some(Json(LoginResponse { user_id: user.0, secret: user.1 })) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct LoginResponse {
|
||||||
|
user_id: u32,
|
||||||
|
secret: String,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user