Compare commits
No commits in common. "6889377f07e840f93ddb75bfc9f0200c4e888040" and "a5388826281a598d33dbd331fe180f7ea18ec443" have entirely different histories.
6889377f07
...
a538882628
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
database.db
|
database.db
|
||||||
.env
|
|
||||||
/scripts
|
|
||||||
|
1254
Cargo.lock
generated
1254
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@ -5,15 +5,5 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argon2 = { version = "0.5.3", features = ["password-hash", "rand"] }
|
argon2 = { version = "0.5.3", features = ["password-hash", "rand"] }
|
||||||
axum = "0.7.5"
|
|
||||||
chrono = "0.4.38"
|
|
||||||
dotenvy = "0.15.7"
|
|
||||||
http = "1.1.0"
|
|
||||||
jsonwebtoken = "9.3.0"
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rusqlite = "0.32.1"
|
rusqlite = "0.32.1"
|
||||||
serde = { version = "1.0.206", features = ["derive"] }
|
|
||||||
serde_json = "1.0.122"
|
|
||||||
tokio = { version = "1.39.2", features = ["full"] }
|
|
||||||
tower-http = { version = "0.5.2", features = ["fs"] }
|
|
||||||
validator = { version = "0.18.1", features = ["derive"] }
|
|
||||||
|
23
Dockerfile
23
Dockerfile
@ -1,23 +0,0 @@
|
|||||||
FROM rust:1.80 AS builder
|
|
||||||
|
|
||||||
WORKDIR /code
|
|
||||||
|
|
||||||
COPY Cargo.toml Cargo.lock ./
|
|
||||||
RUN mkdir src && echo "fn main() {}" > src/main.rs
|
|
||||||
RUN cargo build --release
|
|
||||||
|
|
||||||
COPY src src
|
|
||||||
RUN cargo install --path .
|
|
||||||
|
|
||||||
FROM debian:bookworm-slim AS runtime
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends sqlite3 libsqlite3-dev && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=builder /usr/local/cargo/bin/script_hoster .
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["./script_hoster"]
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
services:
|
|
||||||
web:
|
|
||||||
restart: unless-stopped
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
volumes:
|
|
||||||
- ./.env:/app/.env:ro
|
|
||||||
- data:/app
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
data:
|
|
463
src/api/mod.rs
463
src/api/mod.rs
@ -1,463 +0,0 @@
|
|||||||
use axum::{routing::post, Json, Router};
|
|
||||||
use jsonwebtoken::{encode, DecodingKey, EncodingKey, Header};
|
|
||||||
use rusqlite::Connection;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use validator::Validate;
|
|
||||||
|
|
||||||
use crate::db;
|
|
||||||
|
|
||||||
pub fn get_routes() -> Router {
|
|
||||||
Router::new().nest(
|
|
||||||
"/api",
|
|
||||||
Router::new()
|
|
||||||
.route("/register", post(register))
|
|
||||||
.route("/login", post(login))
|
|
||||||
.route("/change_username", post(change_username))
|
|
||||||
.route("/change_email", post(change_email))
|
|
||||||
.route("/change_password", post(change_password))
|
|
||||||
.route("/delete_account", post(delete_account))
|
|
||||||
.route("/get_scripts", post(get_scripts))
|
|
||||||
.route("/add_script", post(add_script))
|
|
||||||
.route("/delete_script", post(delete_script))
|
|
||||||
.route("/edit_script", post(edit_script))
|
|
||||||
.route("/rename_script", post(rename_script)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Claims {
|
|
||||||
account_id: usize,
|
|
||||||
exp: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Validate)]
|
|
||||||
pub struct RegisterPayload {
|
|
||||||
username: String,
|
|
||||||
#[validate(email)]
|
|
||||||
email: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn register(Json(payload): Json<RegisterPayload>) -> Json<Value> {
|
|
||||||
if payload.password.len() < 8 {
|
|
||||||
Json(json!({ "error": "Password is too short" }))
|
|
||||||
} else if payload.password.len() > 64 {
|
|
||||||
Json(json!({ "error": "Password is too long" }))
|
|
||||||
} else if payload.username.len() < 3 {
|
|
||||||
Json(json!({ "error": "Username is too short" }))
|
|
||||||
} else if payload.username.len() > 32 {
|
|
||||||
Json(json!({ "error": "Username is too long" }))
|
|
||||||
} else if payload.validate().is_err() {
|
|
||||||
Json(json!({ "error": "Invalid email" }))
|
|
||||||
} else {
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
let result = db::add_account(
|
|
||||||
&connection,
|
|
||||||
&payload.username,
|
|
||||||
&payload.email,
|
|
||||||
&payload.password,
|
|
||||||
);
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => Json(json!({ "success": "User created with success" })),
|
|
||||||
Err(rusqlite::Error::SqliteFailure(_, _)) => {
|
|
||||||
Json(json!({ "error": "Username or email already taken" }))
|
|
||||||
}
|
|
||||||
Err(_) => Json(json!({ "error": "Failed to create user" })),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct LoginPayload {
|
|
||||||
username: Option<String>,
|
|
||||||
email: Option<String>,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn login(Json(payload): Json<LoginPayload>) -> Json<Value> {
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
let account_id = if let Some(username) = payload.username {
|
|
||||||
db::get_account_id_from_username(&connection, &username)
|
|
||||||
} else if let Some(email) = payload.email {
|
|
||||||
db::get_account_id_from_email(&connection, &email)
|
|
||||||
} else {
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
return Json(json!({ "error": "Username or email must be provided" }));
|
|
||||||
};
|
|
||||||
|
|
||||||
let account_id = match account_id {
|
|
||||||
Ok(Some(account_id)) => account_id,
|
|
||||||
Ok(None) => {
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
return Json(json!({ "error": "User not found" }));
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
return Json(json!({ "error": "Failed to get user" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let account = db::get_account(&connection, account_id, &payload.password);
|
|
||||||
match account {
|
|
||||||
Ok(Some(_)) => {
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
|
|
||||||
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
|
||||||
let claims = Claims {
|
|
||||||
account_id,
|
|
||||||
exp: (chrono::Utc::now() + chrono::Duration::days(7)).timestamp() as usize,
|
|
||||||
};
|
|
||||||
let token = encode(
|
|
||||||
&Header::default(),
|
|
||||||
&claims,
|
|
||||||
&EncodingKey::from_secret(jwt_secret.as_ref()),
|
|
||||||
)
|
|
||||||
.expect("Failed to encode token");
|
|
||||||
|
|
||||||
Json(json!({ "success": "User logged in with success", "token": token}))
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
Json(json!({ "error": "Wrong password" }))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
Json(json!({ "error": "Failed to get user" }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ChangeUsernamePayload {
|
|
||||||
username: String,
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_account_id_from_jwt(token: &str) -> Result<usize, jsonwebtoken::errors::Error> {
|
|
||||||
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
|
||||||
let token_data = jsonwebtoken::decode::<Claims>(
|
|
||||||
token,
|
|
||||||
&DecodingKey::from_secret(jwt_secret.as_ref()),
|
|
||||||
&jsonwebtoken::Validation::default(),
|
|
||||||
)?;
|
|
||||||
Ok(token_data.claims.account_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn change_username(Json(payload): Json<ChangeUsernamePayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
match db::change_username(&connection, account_id, &payload.username) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(rusqlite::Error::SqliteFailure(_, _)) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Username already taken" }));
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to change username" }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Username changed with success" }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ChangeEmailPayload {
|
|
||||||
email: String,
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn change_email(Json(payload): Json<ChangeEmailPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
match db::change_email(&connection, account_id, &payload.email) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(rusqlite::Error::SqliteFailure(_, _)) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Email already taken" }));
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to change email" }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Email changed with success" }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ChangePasswordPayload {
|
|
||||||
password: String,
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn change_password(Json(payload): Json<ChangePasswordPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
match db::change_password(&connection, account_id, &payload.password) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to change password" }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Password changed with success" }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct GenericTokenPayload {
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_account(Json(payload): Json<GenericTokenPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
match db::delete_account(&connection, account_id) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to delete account" }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Account deleted with success" }))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_scripts(Json(payload): Json<GenericTokenPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
let scripts = db::get_scripts(&connection, account_id);
|
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
match scripts {
|
|
||||||
Ok(scripts) => {
|
|
||||||
let mut result = json!({});
|
|
||||||
for (id, name, last_edit) in scripts {
|
|
||||||
result[id.to_string()] = json!({ "name": name, "last_edit": last_edit });
|
|
||||||
}
|
|
||||||
Json(result)
|
|
||||||
}
|
|
||||||
Err(_) => Json(json!({ "error": "Failed to get scripts" })),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct AddScriptPayload {
|
|
||||||
name: String,
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_script(Json(payload): Json<AddScriptPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
match db::add_script(&connection, &payload.name, account_id) {
|
|
||||||
Ok(_) => {
|
|
||||||
let username = match db::get_account_username(&connection, account_id)
|
|
||||||
.expect("Failed to get account username")
|
|
||||||
{
|
|
||||||
Some(username) => username,
|
|
||||||
None => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to get account username" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let filename = format!("scripts/{}/{}", username, &payload.name);
|
|
||||||
std::fs::create_dir_all(format!("scripts/{}", username))
|
|
||||||
.expect("Failed to create directory");
|
|
||||||
File::create(&filename).expect("Failed to create file");
|
|
||||||
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Script added with success" }))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "error": "Failed to add script" }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct DeleteScriptPayload {
|
|
||||||
script_id: usize,
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_script(Json(payload): Json<DeleteScriptPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
if !db::script_is_owned_by(&connection, payload.script_id, account_id)
|
|
||||||
.expect("Failed to check if script is owned by user")
|
|
||||||
{
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Script is not owned by user" }));
|
|
||||||
}
|
|
||||||
let script_name = match db::get_script_name(&connection, payload.script_id, account_id)
|
|
||||||
.expect("Failed to get script name")
|
|
||||||
{
|
|
||||||
Some(script_name) => script_name,
|
|
||||||
None => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to get script name" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match db::remove_script(&connection, payload.script_id) {
|
|
||||||
Ok(_) => {
|
|
||||||
let username = match db::get_account_username(&connection, account_id)
|
|
||||||
.expect("Failed to get account username")
|
|
||||||
{
|
|
||||||
Some(username) => username,
|
|
||||||
None => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to get account username" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let filename = format!("scripts/{}/{}", username, script_name);
|
|
||||||
std::fs::remove_file(&filename).expect("Failed to remove file");
|
|
||||||
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Script deleted with success" }))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "error": "Failed to delete script" }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct EditScriptPayload {
|
|
||||||
script_id: usize,
|
|
||||||
content: String,
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn edit_script(Json(payload): Json<EditScriptPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
if !db::script_is_owned_by(&connection, payload.script_id, account_id)
|
|
||||||
.expect("Failed to check if script is owned by user")
|
|
||||||
{
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Script is not owned by user" }));
|
|
||||||
}
|
|
||||||
match db::update_script_last_edit(&connection, payload.script_id) {
|
|
||||||
Ok(_) => {
|
|
||||||
let username = match db::get_account_username(&connection, account_id)
|
|
||||||
.expect("Failed to get account username")
|
|
||||||
{
|
|
||||||
Some(username) => username,
|
|
||||||
None => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to get account username" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let script_name = match db::get_script_name(&connection, payload.script_id, account_id)
|
|
||||||
.expect("Failed to get script name")
|
|
||||||
{
|
|
||||||
Some(script_name) => script_name,
|
|
||||||
None => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to get script name" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let filename = format!("scripts/{}/{}", username, script_name);
|
|
||||||
let mut file = File::create(&filename).expect("Failed to open file");
|
|
||||||
file.write_all(payload.content.as_bytes())
|
|
||||||
.expect("Failed to write to file");
|
|
||||||
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Script edited with success" }))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "error": "Failed to edit script" }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct RenameScriptPayload {
|
|
||||||
script_id: usize,
|
|
||||||
new_script_name: String,
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn rename_script(Json(payload): Json<RenameScriptPayload>) -> Json<Value> {
|
|
||||||
let account_id = match get_account_id_from_jwt(&payload.token) {
|
|
||||||
Ok(account_id) => account_id,
|
|
||||||
Err(_) => return Json(json!({ "error": "Invalid token" })),
|
|
||||||
};
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
|
||||||
if !db::script_is_owned_by(&connection, payload.script_id, account_id)
|
|
||||||
.expect("Failed to check if script is owned by user")
|
|
||||||
{
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Script is not owned by user" }));
|
|
||||||
}
|
|
||||||
let old_script_name = match db::get_script_name(&connection, payload.script_id, account_id)
|
|
||||||
.expect("Failed to get script name")
|
|
||||||
{
|
|
||||||
Some(script_name) => script_name,
|
|
||||||
None => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to get script name" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match db::modify_script_name(&connection, payload.script_id, &payload.new_script_name) {
|
|
||||||
Ok(_) => {
|
|
||||||
let username = match db::get_account_username(&connection, account_id)
|
|
||||||
.expect("Failed to get account username")
|
|
||||||
{
|
|
||||||
Some(username) => username,
|
|
||||||
None => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
return Json(json!({ "error": "Failed to get account username" }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let old_filename = format!("scripts/{}/{}", username, old_script_name);
|
|
||||||
let new_filename = format!("scripts/{}/{}", username, &payload.new_script_name);
|
|
||||||
std::fs::rename(&old_filename, &new_filename).expect("Failed to rename file");
|
|
||||||
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "success": "Script renamed with success" }))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
connection.close().expect("Failed to close");
|
|
||||||
Json(json!({ "error": "Failed to rename script" }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
@import url('https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css');
|
|
||||||
|
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
@ -122,17 +122,7 @@ pub fn get_account_id_from_email(conn: &Connection, email: &str) -> Result<Optio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account_username(conn: &Connection, id: usize) -> Result<Option<String>> {
|
pub fn get_scripts(conn: &Connection, account_id: usize) -> Result<Vec<(usize, String, usize)>> {
|
||||||
let mut stmt = conn.prepare("SELECT username FROM accounts WHERE id = ?1")?;
|
|
||||||
let mut rows = stmt.query([id])?;
|
|
||||||
if let Some(row) = rows.next()? {
|
|
||||||
Ok(Some(row.get(0)?))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_scripts(conn: &Connection, account_id: usize) -> Result<Vec<(usize, String, String)>> {
|
|
||||||
let mut stmt = conn.prepare("SELECT id, name, last_edit FROM scripts WHERE account_id = ?1")?;
|
let mut stmt = conn.prepare("SELECT id, name, last_edit FROM scripts WHERE account_id = ?1")?;
|
||||||
let mut rows = stmt.query([account_id])?;
|
let mut rows = stmt.query([account_id])?;
|
||||||
let mut scripts = Vec::new();
|
let mut scripts = Vec::new();
|
||||||
@ -148,7 +138,12 @@ pub fn get_scripts(conn: &Connection, account_id: usize) -> Result<Vec<(usize, S
|
|||||||
Ok(scripts)
|
Ok(scripts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_script(conn: &Connection, name: &str, account_id: usize) -> Result<()> {
|
pub fn add_script(
|
||||||
|
conn: &Connection,
|
||||||
|
name: &str,
|
||||||
|
last_edit: usize,
|
||||||
|
account_id: usize,
|
||||||
|
) -> Result<()> {
|
||||||
let mut stmt = conn.prepare("SELECT id FROM scripts WHERE name = ?1 AND account_id = ?2")?;
|
let mut stmt = conn.prepare("SELECT id FROM scripts WHERE name = ?1 AND account_id = ?2")?;
|
||||||
let mut rows = stmt.query([&name as &dyn ToSql, &account_id as &dyn ToSql])?;
|
let mut rows = stmt.query([&name as &dyn ToSql, &account_id as &dyn ToSql])?;
|
||||||
if rows.next()?.is_some() {
|
if rows.next()?.is_some() {
|
||||||
@ -156,8 +151,8 @@ pub fn add_script(conn: &Connection, name: &str, account_id: usize) -> Result<()
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO scripts (name, account_id) VALUES (?1, ?2)",
|
"INSERT INTO scripts (name, last_edit, account_id) VALUES (?1, ?2, ?3)",
|
||||||
(name, account_id),
|
(name, last_edit, account_id),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -174,15 +169,10 @@ pub fn script_is_owned_by(conn: &Connection, script_id: usize, account_id: usize
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_script_name(
|
pub fn get_script_id(conn: &Connection, name: &str, account_id: usize) -> Result<Option<usize>> {
|
||||||
conn: &Connection,
|
let mut stmt = conn.prepare("SELECT id FROM scripts WHERE name = ?1 AND account_id = ?2")?;
|
||||||
script_id: usize,
|
let mut rows = stmt.query([&name as &dyn ToSql, &account_id as &dyn ToSql])?;
|
||||||
account_id: usize,
|
|
||||||
) -> Result<Option<String>> {
|
|
||||||
let mut stmt = conn.prepare("SELECT name FROM scripts WHERE id = ?1 AND account_id = ?2")?;
|
|
||||||
let mut rows = stmt.query([&script_id as &dyn ToSql, &account_id as &dyn ToSql])?;
|
|
||||||
if let Some(row) = rows.next()? {
|
if let Some(row) = rows.next()? {
|
||||||
println!("row: {:?}", row);
|
|
||||||
Ok(Some(row.get(0)?))
|
Ok(Some(row.get(0)?))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>404 Not found</title>
|
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
|
||||||
</head>
|
|
||||||
<body class="center">
|
|
||||||
<header>
|
|
||||||
<h1>404</h1>
|
|
||||||
<h2>Page not found</h2>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<p>Sorry, the page you are looking for does not exist.</p>
|
|
||||||
<p>If you arrived here via a link, please open an issue on the repository.</p>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,23 +0,0 @@
|
|||||||
use axum::handler::HandlerWithoutStateExt;
|
|
||||||
use axum::response::{Html, IntoResponse};
|
|
||||||
use axum::Router;
|
|
||||||
use http::StatusCode;
|
|
||||||
use tower_http::services::ServeDir;
|
|
||||||
|
|
||||||
pub fn get_routes() -> Router {
|
|
||||||
Router::new()
|
|
||||||
.nest_service(
|
|
||||||
"/",
|
|
||||||
ServeDir::new("src/static_pages").not_found_service(handler.into_service()),
|
|
||||||
)
|
|
||||||
.nest_service("/assets", ServeDir::new("src/assets"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handler() -> impl IntoResponse {
|
|
||||||
let path = "src/error_pages/404.html";
|
|
||||||
if let Ok(content) = tokio::fs::read_to_string(path).await {
|
|
||||||
(StatusCode::NOT_FOUND, Html(content))
|
|
||||||
} else {
|
|
||||||
(StatusCode::NOT_FOUND, Html("404 Not Found".to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
26
src/main.rs
26
src/main.rs
@ -1,32 +1,8 @@
|
|||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
use axum::Router;
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use tower_http::services::ServeDir;
|
|
||||||
|
|
||||||
mod api;
|
|
||||||
mod db;
|
mod db;
|
||||||
mod frontend;
|
|
||||||
|
|
||||||
#[tokio::main]
|
fn main() {
|
||||||
async fn main() {
|
|
||||||
let connection = Connection::open("database.db").expect("Failed to open database");
|
let connection = Connection::open("database.db").expect("Failed to open database");
|
||||||
db::init(&connection).expect("Failed to create database");
|
db::init(&connection).expect("Failed to create database");
|
||||||
connection.close().expect("Failed to close database");
|
|
||||||
|
|
||||||
dotenvy::dotenv().ok();
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.merge(api::get_routes())
|
|
||||||
.merge(frontend::get_routes())
|
|
||||||
.nest_service("/scripts", ServeDir::new("scripts"));
|
|
||||||
|
|
||||||
let port = std::env::var("PORT")
|
|
||||||
.unwrap_or_else(|_| "3000".to_string())
|
|
||||||
.parse()
|
|
||||||
.expect("PORT must be an unsigned number");
|
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
|
||||||
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user