diff --git a/.gitignore b/.gitignore
index 68b3e7e..cfb4bb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
/target
+/data
+
.env
config.toml
\ No newline at end of file
diff --git a/config.example.toml b/config.example.toml
index 20e2599..76fb6e2 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -16,4 +16,8 @@ port = 8000
[accounts]
# allow new accounts to be created
-allow_registration = true
\ No newline at end of file
+allow_registration = true
+
+[db]
+# path for your data to be stored
+data_folder = "data"
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 9d7eb6f..115f664 100644
--- a/readme.md
+++ b/readme.md
@@ -30,11 +30,14 @@ _these features are implemented_
### building
-- run `cargo build`
+- run `cargo build --release`
## todo
- probably work on the code warnings we get hehe
- green name users... (add udid auth to auth function, use userName instead of accountID in uploading levels, and it goes on and on and on and on...)
+- patch uploadlevel to fix color tags, use 1.9 [levelInfo](https://github.com/Cvolton/GMDprivateServer/blob/master/incl/levels/uploadGJLevel.php#L56)???, and use 2.2 unlisted??
+- clean up uploadlevel
+- add updating levels
- add level parsing
- maybe swap to timestamp type instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!)
\ No newline at end of file
diff --git a/src/config.rs b/src/config.rs
index ee9a2aa..c6b37ae 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -5,7 +5,8 @@ use std::sync::LazyLock;
#[derive(Deserialize)]
pub struct Config {
pub general: ConfigGeneral,
- pub accounts: ConfigAccounts
+ pub accounts: ConfigAccounts,
+ pub db: ConfigDB
}
#[derive(Deserialize)]
@@ -19,6 +20,11 @@ pub struct ConfigAccounts {
pub allow_registration: bool
}
+#[derive(Deserialize)]
+pub struct ConfigDB {
+ pub data_folder: String
+}
+
impl Config {
pub fn load_from_file(file_path: &str) -> Self {
let toml_str = fs::read_to_string(file_path).expect("Error finding toml config:");
diff --git a/src/db/models.rs b/src/db/models.rs
index 0dd7f17..b78af61 100644
--- a/src/db/models.rs
+++ b/src/db/models.rs
@@ -121,4 +121,29 @@ pub struct Level {
pub featured: i32,
pub epic: 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,
+ pub requested_stars: i32,
+ pub unlisted: i32,
+ pub version: i32,
+ pub extra_data: Vec,
+ pub level_info: Vec,
+ 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
}
\ No newline at end of file
diff --git a/src/endpoints/levels/upload_level.rs b/src/endpoints/levels/upload_level.rs
index 2444bca..fb930cc 100644
--- a/src/endpoints/levels/upload_level.rs
+++ b/src/endpoints/levels/upload_level.rs
@@ -5,6 +5,10 @@ use rocket::response::status;
use diesel::prelude::*;
+use base64::{Engine as _, engine::general_purpose};
+
+use std::fs;
+
use crate::helpers;
use crate::db;
@@ -12,9 +16,27 @@ use crate::db;
pub struct FormUploadLevel {
accountID: i32,
- password: Option,
gjp: Option,
gjp2: Option,
+
+ password: Option,
+ songID: i32,
+ audioTrack: i32,
+ levelName: String,
+ levelDesc: String,
+ levelID: i32,
+ levelVersion: i32,
+ levelInfo: String,
+ levelString: String,
+ gameVersion: i32,
+ extraString: Option,
+ requestedStars: Option,
+ binaryVersion: Option,
+ unlisted: Option,
+ original: Option,
+ wt: Option,
+ wt2: Option,
+ ldm: Option
}
#[post("/uploadGJLevel21.php", data = "")]
@@ -22,15 +44,99 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str
let connection = &mut db::establish_connection_pg();
// 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)) => {
user_id_val = user_id;
- account_id_val = account_id;
+ _account_id_val = account_id;
},
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::(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::(connection)
+ .expect("couldnt query levels");
+
+ if level_user_id != user_id_val {
+ return status::Custom(Status::Ok, "-1")
+ }
+
+ // TODO: add level to db, file
+
+ return status::Custom(Status::Ok, Box::leak(input.levelID.to_string().into_boxed_str()))
+ } else {
+ // upload level
+ let new_level = NewLevel {
+ // TODO: clean and filter this to 20 charfacters
+ name: input.levelName.clone(),
+ user_id: user_id_val,
+ // TODO: filter this to 140 characters
+ description: description_val,
+ 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::(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")
+ }
+ }
}
\ No newline at end of file
diff --git a/src/helpers.rs b/src/helpers.rs
index 49728fc..dcc409e 100644
--- a/src/helpers.rs
+++ b/src/helpers.rs
@@ -1,4 +1,5 @@
pub mod accounts;
pub mod clean;
pub mod encryption;
-pub mod format;
\ No newline at end of file
+pub mod format;
+pub mod levels;
\ No newline at end of file
diff --git a/src/helpers/levels.rs b/src/helpers/levels.rs
new file mode 100644
index 0000000..d85a395
--- /dev/null
+++ b/src/helpers/levels.rs
@@ -0,0 +1,7 @@
+use std::sync::LazyLock;
+
+pub static DEFAULT_EXTRA_STRING: LazyLock = LazyLock::new(|| {
+ let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29");
+
+ return string;
+});
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 6c30eb8..d9dfe45 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,6 +4,8 @@
#[macro_use] extern crate maplit;
#[macro_use] extern crate rocket;
+use std::fs;
+
mod db;
use db::*;
@@ -23,6 +25,8 @@ fn index() -> String {
#[launch]
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()
.configure(rocket::Config::figment().merge(("port", CONFIG.general.port)))
.mount("/", routes![