Compare commits

..

2 commits

Author SHA1 Message Date
af1e423b56
yeah you can update levels now cool 2023-09-04 21:26:13 -07:00
ac8a121a8c
uploading levels i think 2023-09-04 19:51:37 -07:00
12 changed files with 198 additions and 15 deletions

2
.gitignore vendored
View file

@ -1,4 +1,6 @@
/target /target
/data
.env .env
config.toml config.toml

View file

@ -16,4 +16,8 @@ port = 8000
[accounts] [accounts]
# allow new accounts to be created # allow new accounts to be created
allow_registration = true allow_registration = true
[db]
# path for your data to be stored
data_folder = "data"

View file

@ -30,11 +30,12 @@ _these features are implemented_
### building ### building
- run `cargo build` - run `cargo build --release`
## todo ## todo
- probably work on the code warnings we get hehe - <small>green name users...</small>
- <small>green name users...</small> (add udid auth to auth function, use userName instead of accountID in uploading levels, and it goes on and on and on <small>and on...</small>) - patch uploadlevel to use 2.2 unlisted(honsetly idk what this is)
- clean up uploadlevel
- add level parsing - add level parsing
- maybe swap to timestamp type instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!) - maybe swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!)

View file

@ -5,7 +5,8 @@ use std::sync::LazyLock;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
pub general: ConfigGeneral, pub general: ConfigGeneral,
pub accounts: ConfigAccounts pub accounts: ConfigAccounts,
pub db: ConfigDB
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -19,6 +20,11 @@ pub struct ConfigAccounts {
pub allow_registration: bool pub allow_registration: bool
} }
#[derive(Deserialize)]
pub struct ConfigDB {
pub data_folder: String
}
impl Config { impl Config {
pub fn load_from_file(file_path: &str) -> Self { pub fn load_from_file(file_path: &str) -> Self {
let toml_str = fs::read_to_string(file_path).expect("Error finding toml config:"); let toml_str = fs::read_to_string(file_path).expect("Error finding toml config:");

View file

@ -121,4 +121,29 @@ pub struct Level {
pub featured: i32, pub featured: i32,
pub epic: i32, pub epic: i32,
pub rated_coins: i32 pub rated_coins: i32
}
#[derive(Insertable, Deserialize)]
#[diesel(table_name = levels)]
pub struct NewLevel {
pub name: String,
pub user_id: i32,
pub description: String,
pub original: i32,
pub game_version: i32,
pub binary_version: i32,
pub password: Option<String>,
pub requested_stars: i32,
pub unlisted: i32,
pub version: i32,
pub extra_data: Vec<u8>,
pub level_info: Vec<u8>,
pub editor_time: i32,
pub editor_time_copies: i32,
pub song_id: i32,
pub length: i32,
pub objects: i32,
pub coins: i32,
pub has_ldm: i32,
pub two_player: i32
} }

View file

@ -20,7 +20,7 @@ pub struct FromLoginAccount {
pub fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> { pub fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg(); let connection = &mut db::establish_connection_pg();
if input.userName != helpers::clean::clean(input.userName.as_ref()) { if input.userName != helpers::clean::clean_no_space(input.userName.as_ref()) {
return status::Custom(Status::Ok, "-4") return status::Custom(Status::Ok, "-4")
} }

View file

@ -26,7 +26,7 @@ pub fn register_account(input: Form<FormRegisterAccount>) -> status::Custom<&'st
return status::Custom(Status::Ok, "-1") return status::Custom(Status::Ok, "-1")
} }
if input.userName != helpers::clean::clean(input.userName.as_ref()) { if input.userName != helpers::clean::clean_no_space(input.userName.as_ref()) {
return status::Custom(Status::Ok, "-4") return status::Custom(Status::Ok, "-4")
} }

View file

@ -5,6 +5,10 @@ use rocket::response::status;
use diesel::prelude::*; use diesel::prelude::*;
use base64::{Engine as _, engine::general_purpose};
use std::fs;
use crate::helpers; use crate::helpers;
use crate::db; use crate::db;
@ -12,9 +16,27 @@ use crate::db;
pub struct FormUploadLevel { pub struct FormUploadLevel {
accountID: i32, accountID: i32,
password: Option<String>,
gjp: Option<String>, gjp: Option<String>,
gjp2: Option<String>, gjp2: Option<String>,
password: Option<String>,
songID: i32,
audioTrack: i32,
levelName: String,
levelDesc: String,
levelID: i32,
levelVersion: i32,
levelInfo: String,
levelString: String,
gameVersion: i32,
extraString: Option<String>,
requestedStars: Option<i32>,
binaryVersion: Option<i32>,
unlisted: Option<i32>,
original: Option<i32>,
wt: Option<i32>,
wt2: Option<i32>,
ldm: Option<i32>
} }
#[post("/uploadGJLevel21.php", data = "<input>")] #[post("/uploadGJLevel21.php", data = "<input>")]
@ -22,15 +44,121 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
let connection = &mut db::establish_connection_pg(); let connection = &mut db::establish_connection_pg();
// account verification // account verification
let (user_id_val, account_id_val): (i32, i32); let (user_id_val, _account_id_val): (i32, i32);
match helpers::accounts::auth(input.accountID.clone(), input.password.clone(), input.gjp.clone(), input.gjp2.clone()) { // password argument is used for the level, so
match helpers::accounts::auth(input.accountID.clone(), None, input.gjp.clone(), input.gjp2.clone()) {
Ok((user_id, account_id)) => { Ok((user_id, account_id)) => {
user_id_val = user_id; user_id_val = user_id;
account_id_val = account_id; _account_id_val = account_id;
}, },
Err(_) => return status::Custom(Status::Ok, "-1") Err(_) => return status::Custom(Status::Ok, "-1")
}; };
return status::Custom(Status::Ok, "1") let description_val;
if input.gameVersion >= 20 {
description_val = String::from_utf8(general_purpose::URL_SAFE.decode(input.levelDesc.clone()).expect("couldn't decode base64")).expect("invalid UTF-8 sequence (how)")
} else {
description_val = input.levelDesc.clone()
}
let song_id_val = if input.songID == 0 {
input.audioTrack
} else {
input.songID
};
let extra_string;
match input.extraString.clone() {
Some(extra_string_val) => { extra_string = extra_string_val },
None => { extra_string = helpers::levels::DEFAULT_EXTRA_STRING.to_owned() }
}
// db shit
use crate::models::{Level, NewLevel};
{
use crate::schema::levels::dsl::*;
if levels
.filter(id.eq(input.levelID))
.count()
.get_result::<i64>(connection)
.expect("couldnt get count of levels") > 0 {
// update level
let level_user_id = levels
.filter(id.eq(input.levelID))
.select(user_id)
.get_result::<i32>(connection)
.expect("couldnt query levels");
if level_user_id != user_id_val {
return status::Custom(Status::Ok, "-1")
}
let updated_level = diesel::update(levels)
.filter(id.eq(input.levelID))
.set((
description.eq(description_val.chars().take(140).collect::<String>()),
password.eq(input.password.clone()),
requested_stars.eq(match input.requestedStars {
Some(requested_stars_val) => requested_stars_val.clamp(0, 10),
None => 0
}),
version.eq(input.levelVersion),
extra_data.eq(extra_string.as_bytes().to_owned()),
level_info.eq(input.levelInfo.clone().into_bytes()),
editor_time.eq(input.wt.unwrap_or(0)),
editor_time_copies.eq(input.wt2.unwrap_or(0)),
song_id.eq(song_id_val),
length.eq(0), // unimplemeneted
objects.eq(0), // unimplemented
coins.eq(0), // unimplemented
has_ldm.eq(input.ldm.unwrap_or(0)),
two_player.eq(0) // unimplemented
))
.get_result::<Level, >(connection)
.expect("failed to update level");
fs::write(format!("{}/levels/{}.lvl", crate::CONFIG.db.data_folder, updated_level.id), general_purpose::URL_SAFE.decode(input.levelString.clone()).expect("user provided invalid level string")).expect("couldnt write level to file");
return status::Custom(Status::Ok, Box::leak(input.levelID.to_string().into_boxed_str()))
} else {
// upload level
let new_level = NewLevel {
name: helpers::clean::clean_basic(&input.levelName).chars().take(20).collect(),
user_id: user_id_val,
description: description_val.chars().take(140).collect(),
original: input.original.unwrap_or(0),
game_version: input.gameVersion,
binary_version: input.binaryVersion.unwrap_or(0),
password: input.password.clone(),
requested_stars: match input.requestedStars {
Some(requested_stars_val) => requested_stars_val.clamp(0, 10),
None => 0
},
unlisted: input.unlisted.unwrap_or(0),
version: input.levelVersion,
extra_data: extra_string.as_bytes().to_owned(),
level_info: input.levelInfo.clone().into_bytes(),
editor_time: input.wt.unwrap_or(0),
editor_time_copies: input.wt2.unwrap_or(0),
song_id: song_id_val,
length: 0, // not implemeneted
objects: 0, // not implemeneted
coins: 0, // not implemeneted
has_ldm: input.ldm.unwrap_or(0),
two_player: 0 // not implemented
};
let inserted_level = diesel::insert_into(levels)
.values(&new_level)
.get_result::<Level, >(connection)
.expect("failed to insert level");
fs::write(format!("{}/levels/{}.lvl", crate::CONFIG.db.data_folder, inserted_level.id), general_purpose::URL_SAFE.decode(input.levelString.clone()).expect("user provided invalid level string")).expect("couldnt write level to file");
return status::Custom(Status::Ok, "1")
}
}
} }

View file

@ -1,4 +1,5 @@
pub mod accounts; pub mod accounts;
pub mod clean; pub mod clean;
pub mod encryption; pub mod encryption;
pub mod format; pub mod format;
pub mod levels;

View file

@ -1,6 +1,11 @@
use regex::Regex; use regex::Regex;
pub fn clean(string: &str) -> String { pub fn clean_no_space(string: &str) -> String {
let regex = Regex::new(r"[^a-zA-z0-9_-]").unwrap(); let regex = Regex::new(r"[^a-zA-z0-9_-]").unwrap();
return regex.replace_all(string, "").to_string(); return regex.replace_all(string, "").to_string();
}
pub fn clean_basic(string: &str) -> String {
let regex = Regex::new(r"[^A-Za-z0-9\-_ ]").unwrap();
return regex.replace_all(string, "").to_string();
} }

7
src/helpers/levels.rs Normal file
View file

@ -0,0 +1,7 @@
use std::sync::LazyLock;
pub static DEFAULT_EXTRA_STRING: LazyLock<String> = LazyLock::new(|| {
let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29");
return string;
});

View file

@ -4,6 +4,8 @@
#[macro_use] extern crate maplit; #[macro_use] extern crate maplit;
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use std::fs;
mod db; mod db;
use db::*; use db::*;
@ -23,6 +25,8 @@ fn index() -> String {
#[launch] #[launch]
fn rocket() -> _ { fn rocket() -> _ {
fs::create_dir_all(&CONFIG.db.data_folder).expect("failed to create data directory!");
fs::create_dir_all(format!("{}/levels", &CONFIG.db.data_folder)).expect("failed to create data directory for levels");
rocket::build() rocket::build()
.configure(rocket::Config::figment().merge(("port", CONFIG.general.port))) .configure(rocket::Config::figment().merge(("port", CONFIG.general.port)))
.mount("/", routes![ .mount("/", routes![