Initial commit

This commit is contained in:
2025-07-28 20:54:58 +10:00
commit 57e44f0a2b
4 changed files with 1058 additions and 0 deletions

214
src/main.rs Normal file
View File

@@ -0,0 +1,214 @@
use axum::{
Router, extract::Json, http::StatusCode, response::IntoResponse, routing::get, routing::post,
};
use rusqlite::{Connection, Error as RusqliteError, Result, params};
use serde::{Deserialize, Serialize};
use sha256::digest;
use std::error::Error;
use std::fmt;
use std::path::Path;
#[derive(Debug)]
enum AccError {
WrongPassword,
UnknownAccount,
DatabaseError(RusqliteError),
}
impl fmt::Display for AccError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AccError::WrongPassword => write!(f, "Incorrect password provided"),
AccError::UnknownAccount => write!(f, "Account not found"),
AccError::DatabaseError(e) => write!(f, "Database error: {}", e),
}
}
}
impl Error for AccError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
AccError::DatabaseError(e) => Some(e),
_ => None,
}
}
}
impl From<RusqliteError> for AccError {
fn from(err: RusqliteError) -> AccError {
AccError::DatabaseError(err)
}
}
#[derive(Debug, Serialize, Deserialize)]
struct NewPerson {
name: String,
password: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct GetID {
name: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct GetName {
id: i32,
}
#[derive(Debug, Serialize, Deserialize)]
struct SendMoney {
sender: i32,
password: String,
reciever: i32,
amount: i64,
message: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct Person {
id: i32,
name: String,
password: String,
money: i64,
}
fn init_database() -> Result<()> {
println!("Initing database");
let conn = Connection::open("financials.db")?;
conn.execute(
"CREATE TABLE persons (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
money INTEGER
)",
(),
)?;
Ok(())
}
fn make_person(person: &NewPerson) -> Result<()> {
let conn = Connection::open("financials.db")?;
conn.execute(
"INSERT INTO persons (name, password, money) VALUES (?1, ?2, ?3)",
(&person.name, &digest(person.password.clone()), 1000),
)?;
println!("Successfully inserted person into database!");
Ok(())
}
fn send_money_to_id(request: &SendMoney) -> Result<(), AccError> {
let conn = Connection::open("financials.db")?;
let result = conn.query_row(
"SELECT * FROM persons WHERE id = ?1 AND password = ?2",
params![&request.sender, &digest(request.password.clone())],
|_row| Ok(()),
);
match result {
Ok(()) => {
println!("Found a person! Yay!");
conn.execute(
"UPDATE persons SET money = money - ?1 WHERE id = ?2",
params![request.amount, request.sender],
)?;
conn.execute(
"UPDATE persons SET money = money + ?1 WHERE id = ?2",
params![request.amount, request.reciever],
)?;
}
Err(RusqliteError::QueryReturnedNoRows) => {
let result2 = conn.query_row(
"SELECT * FROM persons WHERE id = ?1",
params![&request.sender],
|_row| Ok(()),
);
match result2 {
Ok(()) => {
return Err(AccError::WrongPassword);
}
Err(RusqliteError::QueryReturnedNoRows) => {
return Err(AccError::UnknownAccount);
}
Err(e) => {
println!("Error: {:?}", e);
}
}
}
Err(e) => {
println!("Error: {:?}", e);
return Err(AccError::DatabaseError(e));
}
}
Ok(())
}
async fn process_request() -> String {
"dingle".to_string()
}
async fn create_person_request(Json(payload): Json<NewPerson>) -> impl IntoResponse {
match make_person(&payload) {
Ok(_) => (
StatusCode::CREATED,
"Person created successfully!".to_string(),
),
Err(e) => {
eprintln!("Failed to create person: {}", e);
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to create person: {}", e),
)
}
}
}
async fn send_money_request(Json(payload): Json<SendMoney>) -> impl IntoResponse {
match send_money_to_id(&payload) {
Ok(_) => (
StatusCode::ACCEPTED,
"Transaction was successful!".to_string(),
),
Err(e) => (
StatusCode::IM_A_TEAPOT,
format!("Transaction failed! Error: {}", e),
),
}
}
fn get_id_by_name(name: &GetID) -> Result<i32, AccError> {
let conn = Connection::open("financials.db")?;
let id = conn.query_row(
"SELECT id FROM persons WHERE name = ?1",
params![name.name],
|row| row.get(0),
)?;
Ok(id)
}
async fn get_id_request(Json(payload): Json<GetID>) -> impl IntoResponse {
let id = get_id_by_name(&payload);
match id {
Ok(_) => (StatusCode::OK, id.unwrap().to_string()),
Err(e) => (StatusCode::NOT_FOUND, format!("Error: {}", e)),
}
}
#[tokio::main]
async fn main() {
println!("Server starting up...");
if !Path::new("financials.db").exists() {
if init_database() != Ok(()) {
panic!(
"Couldn't init database! Check to see if you've got permissions to write the database in the current directory."
);
};
};
let app = Router::new()
.route("/", get(process_request))
.route("/api/createperson", post(create_person_request))
.route("/api/sendmoneytoid", post(send_money_request))
.route("/api/getidfromname", post(get_id_request));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}