diesel -> sqlx, patch cve (again lul)

This commit is contained in:
Reid 2023-10-31 19:40:49 -07:00
parent a5cac129dd
commit c3bb6d0d67
Signed by: reidlab
GPG key ID: 6C9EAA3364F962C8
39 changed files with 1581 additions and 1207 deletions

View file

@ -1 +1 @@
DATABASE_URL=postgres://username:password@localhost/diesel_demo
DATABASE_URL=sqlite://./database.db

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
/target
/data
*.db
.env
config.toml

899
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,10 +5,10 @@ edition = "2021"
[dependencies]
base64 = "0.21.3"
diesel = { version = "=2.1.0", features = ["postgres", "64-column-tables"] }
dotenvy = "0.15.7"
flate2 = "1.0.27"
maplit = "1.0.2"
migrate = "0.2.0"
password-auth = "0.3.0"
rand = "0.8.5"
regex = "1.9.4"
@ -19,4 +19,6 @@ roxmltree = "0.18.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
sha = "1.0.3"
sqlx = { version = "0.7.2", features = ["runtime-tokio", "sqlite"] }
tempfile = "3.8.0"
toml = "0.7.6"

5
build.rs Normal file
View file

@ -0,0 +1,5 @@
// generated by `sqlx migrate build-script`
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}

View file

@ -9,10 +9,17 @@
# boomlings.com/database/
# example.com/aaaaaaaaaa/
# ^^^^^^^^^^^
# leaving as "/" will disable this
append_path = "/"
# where can your server be accessible?
# leaving as empty will disable this
append_path = ""
# where can your server be accessible (port)?
port = 8000
# where can your server be accessible (domain)?
# this is used for patching the executable
#
# example:
# boomlings.com
# localhost:8000
domain = "localhost:8000"
[accounts]
# allow new accounts to be created

View file

@ -1,9 +0,0 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/db/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId"]
[migrations_directory]
dir = "migrations"

View file

View file

@ -1,6 +0,0 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

View file

@ -1,36 +0,0 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View file

@ -1,3 +0,0 @@
DROP TABLE accounts;
DROP COLLATION case_insensitive;

View file

@ -0,0 +1 @@
DROP TABLE accounts;

View file

@ -1,13 +1,7 @@
CREATE COLLATION case_insensitive (
provider = icu,
locale = 'und-u-ks-level2',
deterministic = false
);
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
id INTEGER NOT NULL PRIMARY KEY,
username VARCHAR(20) NOT NULL COLLATE case_insensitive UNIQUE,
username VARCHAR(20) NOT NULL COLLATE NOCASE UNIQUE,
gjp2 TEXT NOT NULL, -- argon2 hashed (rubrub uses bcrypt but oh well)
password TEXT NOT NULL, -- argon2 hashed (rubrub uses bcrypt but oh well)
email VARCHAR(254) NOT NULL,
@ -25,5 +19,5 @@ CREATE TABLE accounts (
twitter_url VARCHAR(20),
twitch_url VARCHAR(20),
created_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now'))
);

View file

@ -1,14 +1,18 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
id INTEGER NOT NULL PRIMARY KEY,
-- if `registered`, use account_id, else, use udid
-- on a registered account, account_id refers to the
-- account ID - however, pre 2.0, instead udid referred
-- to the user's UUID or UDID, depending on platform.
-- UUID and UDID are unique ids assigned for green
-- username users
--
-- in short, if `registered`, use account_id, else, use udid
udid TEXT,
account_id INTEGER references accounts(id),
registered INTEGER NOT NULL,
-- idk why but we get weird errors if we use `COLLATE case_insensitive`
-- maybe troubleshoot later, this works fine for now.
username TEXT NOT NULL, -- COLLATE case_insensitive,
username TEXT NOT NULL COLLATE NOCASE,
stars INTEGER NOT NULL DEFAULT 0,
demons INTEGER NOT NULL DEFAULT 0,
@ -34,8 +38,8 @@ CREATE TABLE users (
special INTEGER NOT NULL DEFAULT 0,
glow INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
last_played TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
last_played TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
is_banned INTEGER NOT NULL DEFAULT 0,
is_banned_upload INTEGER NOT NULL DEFAULT 0

View file

@ -1,11 +1,11 @@
CREATE TABLE levels (
id SERIAL PRIMARY KEY,
created_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
modified_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
id INTEGER NOT NULL PRIMARY KEY,
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
modified_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
name VARCHAR(20) NOT NULL,
user_id INTEGER NOT NULL references users(id),
description VARCHAR(140) NOT NULL DEFAULT '',
description VARCHAR(140) NOT NULL DEFAULT "",
original INTEGER,
game_version INTEGER NOT NULL,
@ -16,8 +16,8 @@ CREATE TABLE levels (
unlisted INTEGER NOT NULL DEFAULT 0,
version INTEGER NOT NULL DEFAULT 0,
extra_data BYTEA NOT NULL,
level_info BYTEA NOT NULL,
extra_data BLOB NOT NULL,
level_info BLOB NOT NULL,
editor_time INTEGER NOT NULL,
editor_time_copies INTEGER NOT NULL,

View file

@ -6,7 +6,7 @@ this project is based off of (stolen from) the [crystal-gauntlet](https://git.oa
## why?
i've run out of ideas.
i'm trying to learn some rust, and this is a solid choice. most GDPS solutions out there are pretty garbage, to say the least.
### features
@ -17,35 +17,40 @@ i've run out of ideas.
## build
### migrating databases
`cargo build --release`
- run `cargo install diesel_cli --no-default-features --features postgres`
- run `diesel migration run`
## configuration
### testing
copy `.env.example` to `.env` and fill it out, same for `config.example.toml` to `config.toml`
- run `cargo run`
## setup
### building
make sure you have `sqlx-cli` by running `cargo install sqlx-cli`
- run `cargo build --release`
run `sqlx database setup`
then finally run the server with `cargo run`
## CLI tools
add `--help` or `-h` when calling the binary for special tools, such as patching executables.
## todo
- User icons in account management pages
- Flesh out account management page
- Account settings page
- Better web design (make formatting more consistant)
- Use chrono for dates in database, add recent
- add get levels search back (find how to easily do dynamic queries)
- user icons in account management pages
- account settings page
- better web design (make formatting more consistant)
- use chrono for dates in database
- 2.2's friends only unlisted
- Dailies, weeklies, events(?)
- Moderation utilities
- Better song support
- Cache authentication
- Panic less
- Make a proper rank system (reuploading, uploading music, rating, etc.)
- Use serde to make the forms whateverCaseThisIs rather than breaking our lint convention
- Swap to `sqlx` im gonna be honest `diesel` is pretty shit.
- Swap to `sqlite` from `postgres`. Postgres feels too clunky and it just solos honestly
- Add back `realip` header support
- Add configurable form limits
- dailies, weeklies, events(?)
- moderation utilities
- better song support
- cache authentication (redis or mem)
- make a proper rank system (reuploading, uploading music, rating, etc.)
- use serde to make the forms whateverCaseThisIs rather than breaking our lint convention
- add back `realip` header support
- add configurable form limits
- nix
- clean up difficulty/demon difficulties. It's fucking VILE.
- panic less, use results

View file

@ -4,6 +4,8 @@ use std::sync::LazyLock;
use toml::Table;
use serde::de::DeserializeOwned;
pub static CONFIG: LazyLock<Table> = LazyLock::new(|| {
let toml_str = fs::read_to_string("config.toml").expect("error finding toml config");
let config: Table = toml::from_str(toml_str.as_str()).expect("error parsing toml config");
@ -13,15 +15,20 @@ pub static CONFIG: LazyLock<Table> = LazyLock::new(|| {
pub fn config_get(key: &str) -> Option<&toml::Value> {
let this = &CONFIG;
let mut current = this.get(key)?;
let mut current_key = this.get(key.split(".").next()?)?;
for val in key.split(".").skip(1) {
current = current.as_table()?.get(val)?;
current_key = current_key.get(val)?;
}
Some(current)
Some(current_key)
}
pub fn config_get_with_default<T: serde::Deserialize<'static>>(key: &str, default: T) -> T {
config_get(key)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(default)
pub fn config_get_with_default<'de, T>(key: &str, default: T) -> T
where
T: DeserializeOwned + 'de
{
let val = config_get(key)
.and_then(|v| v.to_owned().try_into().expect("invalid toml val"))
.unwrap_or_else(|| default);
return val;
}

View file

@ -1,14 +1,11 @@
use diesel::prelude::*;
use sqlx::{Connection, SqliteConnection};
use dotenvy::dotenv;
use std::env;
pub mod models;
pub mod schema;
pub fn establish_connection_pg() -> PgConnection {
pub async fn establish_sqlite_conn() -> SqliteConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
return SqliteConnection::connect(&database_url).await.expect("failed to connect to database");
}

View file

@ -1,149 +0,0 @@
use diesel::prelude::*;
use serde::{Serialize, Deserialize};
use super::schema::*;
#[derive(Queryable, Serialize)]
pub struct Account {
pub id: i32,
pub username: String,
pub password: String,
pub gjp2: String,
pub email: String,
pub is_admin: i32,
pub messages_enabled: i32,
pub comments_enabled: i32,
pub friend_requests_enabled: i32,
pub youtube_url: Option<String>,
pub twitter_url: Option<String>,
pub twitch_url: Option<String>,
pub created_at: String
}
#[derive(Insertable, Deserialize)]
#[diesel(table_name = accounts)]
pub struct NewAccount {
pub username: String,
pub gjp2: String,
pub password: String,
pub email: String,
}
#[derive(Queryable, Serialize)]
pub struct User {
pub id: i32,
pub udid: Option<String>,
pub account_id: Option<i32>,
pub registered: i32,
pub username: String,
pub stars: i32,
pub demons: i32,
pub coins: i32,
pub user_coins: i32,
pub diamonds: i32,
pub orbs: i32,
pub creator_points: i32,
pub completed_levels: i32,
pub icon_type: i32,
pub color1: i32,
pub color2: i32,
pub cube: i32,
pub ship: i32,
pub ball: i32,
pub ufo: i32,
pub wave: i32,
pub robot: i32,
pub spider: i32,
pub explosion: i32,
pub special: i32,
pub glow: i32,
pub created_at: String,
pub last_played: String,
pub is_banned: i32,
pub is_banned_upload: i32
}
#[derive(Insertable, Deserialize)]
#[diesel(table_name = users)]
pub struct NewUser {
pub account_id: i32,
pub username: String,
pub registered: i32
}
#[derive(Queryable, Serialize)]
pub struct Level {
pub id: i32,
pub created_at: String,
pub modified_at: String,
pub name: String,
pub user_id: i32,
pub description: String,
pub original: Option<i32>,
pub game_version: i32,
pub binary_version: i32,
pub password: Option<String>,
pub requested_stars: Option<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,
pub downloads: i32,
pub likes: i32,
pub difficulty: Option<i32>,
pub community_difficulty: Option<i32>,
pub demon_difficulty: Option<i32>,
pub stars: Option<i32>,
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: Option<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

@ -1,107 +0,0 @@
// @generated automatically by Diesel CLI.
diesel::table! {
accounts (id) {
id -> Int4,
#[max_length = 20]
username -> Varchar,
gjp2 -> Text,
password -> Text,
#[max_length = 254]
email -> Varchar,
is_admin -> Int4,
messages_enabled -> Int4,
comments_enabled -> Int4,
friend_requests_enabled -> Int4,
#[max_length = 30]
youtube_url -> Nullable<Varchar>,
#[max_length = 20]
twitter_url -> Nullable<Varchar>,
#[max_length = 20]
twitch_url -> Nullable<Varchar>,
created_at -> Text,
}
}
diesel::table! {
levels (id) {
id -> Int4,
created_at -> Text,
modified_at -> Text,
#[max_length = 20]
name -> Varchar,
user_id -> Int4,
#[max_length = 140]
description -> Varchar,
original -> Nullable<Int4>,
game_version -> Int4,
binary_version -> Int4,
password -> Nullable<Text>,
requested_stars -> Nullable<Int4>,
unlisted -> Int4,
version -> Int4,
extra_data -> Bytea,
level_info -> Bytea,
editor_time -> Int4,
editor_time_copies -> Int4,
song_id -> Int4,
length -> Int4,
objects -> Int4,
coins -> Int4,
has_ldm -> Int4,
two_player -> Int4,
downloads -> Int4,
likes -> Int4,
difficulty -> Nullable<Int4>,
community_difficulty -> Nullable<Int4>,
demon_difficulty -> Nullable<Int4>,
stars -> Nullable<Int4>,
featured -> Int4,
epic -> Int4,
rated_coins -> Int4,
}
}
diesel::table! {
users (id) {
id -> Int4,
udid -> Nullable<Text>,
account_id -> Nullable<Int4>,
registered -> Int4,
username -> Text,
stars -> Int4,
demons -> Int4,
coins -> Int4,
user_coins -> Int4,
diamonds -> Int4,
orbs -> Int4,
creator_points -> Int4,
completed_levels -> Int4,
icon_type -> Int4,
color1 -> Int4,
color2 -> Int4,
cube -> Int4,
ship -> Int4,
ball -> Int4,
ufo -> Int4,
wave -> Int4,
robot -> Int4,
spider -> Int4,
explosion -> Int4,
special -> Int4,
glow -> Int4,
created_at -> Text,
last_played -> Text,
is_banned -> Int4,
is_banned_upload -> Int4,
}
}
diesel::joinable!(levels -> users (user_id));
diesel::joinable!(users -> accounts (account_id));
diesel::allow_tables_to_appear_in_same_query!(
accounts,
levels,
users,
);

View file

@ -1,3 +1,2 @@
pub mod login_account;
pub mod register_account;
pub mod update_account_settings;

View file

@ -2,8 +2,6 @@ use rocket::form::Form;
use rocket::http::Status;
use rocket::response::status;
use diesel::prelude::*;
use crate::helpers;
use crate::db;
@ -17,46 +15,43 @@ pub struct FromLoginAccount {
}
#[post("/accounts/loginGJAccount.php", data = "<input>")]
pub fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg();
pub async fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> {
let connection = &mut db::establish_sqlite_conn().await;
if input.userName != helpers::clean::clean_no_space(input.userName.as_ref()) {
let username = helpers::clean::clean_basic(input.userName.as_ref());
let password = input.password.clone();
let gjp = input.gjp.clone();
let gjp2 = input.gjp2.clone();
if input.userName != username {
return status::Custom(Status::Ok, "-4")
}
// gjp2 checks dont matter, its hashed, gjp checks would break bc its base64, and why does this check exist if its just for logging in robtop this is useless it doesnt provide security we already did the security on the register account u fucking faggot im really bored of working on this but im also excited to see if it works deepwoken solos mid dash
match input.password.clone() {
Some(password_val) => {
if password_val.len() < 6 {
// why does this check exist? it's kinda useless
if let Some(password) = password {
if password.len() < 6 {
return status::Custom(Status::Ok, "-8")
}
},
None => {}
}
if input.userName.len() < 3 {
if username.len() < 3 {
return status::Custom(Status::Ok, "-9")
}
// account verification
{
use db::schema::accounts::dsl::*;
let result = sqlx::query_scalar!("SELECT id FROM accounts WHERE username = ?", username)
.fetch_one(connection)
.await;
let query_result = accounts
.select(id)
.filter(username.eq(input.userName.clone()))
.get_result::<i32, >(connection);
match query_result {
match result {
Ok(account_id_val) => {
let user_id_val = helpers::accounts::get_user_id_from_account_id(account_id_val);
let user_id_val = helpers::accounts::get_user_id_from_account_id(account_id_val).await;
match helpers::accounts::auth(account_id_val, input.password.clone(), input.gjp.clone(), input.gjp2.clone()) {
match helpers::accounts::auth(account_id_val, input.password.clone(), input.gjp.clone(), input.gjp2.clone()).await {
Ok(_) => return status::Custom(Status::Ok, Box::leak(format!("{},{}", user_id_val, account_id_val).into_boxed_str())),
Err(_) => return status::Custom(Status::Ok, "-11")
}
},
Err(_) => return status::Custom(Status::Ok, "-1")
}
}
}

View file

@ -2,9 +2,6 @@ use rocket::form::Form;
use rocket::http::Status;
use rocket::response::status;
use diesel::prelude::*;
use diesel::result::Error;
use password_auth::generate_hash;
use crate::config;
@ -19,77 +16,59 @@ pub struct FormRegisterAccount {
}
#[post("/accounts/registerGJAccount.php", data = "<input>")]
pub fn register_account(input: Form<FormRegisterAccount>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg();
pub async fn register_account(input: Form<FormRegisterAccount>) -> status::Custom<&'static str> {
let mut connection = db::establish_sqlite_conn().await;
let username = helpers::clean::clean_basic(input.userName.as_ref());
let password = input.password.clone();
let email = input.email.clone();
let hashed_password = generate_hash(password.clone());
let gjp2 = helpers::encryption::get_gjp2(password.clone());
if config::config_get_with_default("accounts.allow_registration", true) == false {
return status::Custom(Status::Ok, "-1")
}
if input.userName != helpers::clean::clean_no_space(input.userName.as_ref()) {
if input.userName != username {
return status::Custom(Status::Ok, "-4")
}
if input.password.len() < 6 {
if password.len() < 6 {
return status::Custom(Status::Ok, "-8")
}
if input.userName.len() < 3 {
if username.len() < 3 {
return status::Custom(Status::Ok, "-9")
}
if input.userName.len() > 20 {
if username.len() > 20 {
return status::Custom(Status::Ok, "-4")
}
if input.email.len() > 254 {
if email.len() > 254 {
return status::Custom(Status::Ok, "-6")
}
// account management
use db::models::{Account, NewAccount};
// check if the username is already taken
sqlx::query_scalar!("SELECT COUNT(*) FROM accounts WHERE username = ?", username)
.fetch_one(&mut connection)
.await
.map_err(|_| status::Custom(Status::Ok, "-1"))
.expect("error getting the account count");
let inserted_account: Account;
{
use db::schema::accounts::dsl::*;
let account_name_usage = accounts.filter(username.eq(input.userName.clone())).count().get_result::<i64>(connection) as Result<i64, Error>;
let account_name_used = account_name_usage.expect("database name query error") != 0;
if account_name_used {
return status::Custom(Status::Ok, "-2")
}
let new_account = NewAccount {
username: input.userName.clone(),
password: generate_hash(input.password.clone()),
gjp2: generate_hash(helpers::encryption::get_gjp2(input.password.clone())),
email: input.email.clone()
};
inserted_account = diesel::insert_into(accounts)
.values(&new_account)
.get_result::<Account, >(connection)
let inserted_account = sqlx::query!("INSERT INTO accounts (username, password, email, gjp2) VALUES (?, ?, ?, ?)", username, hashed_password, email, gjp2)
.execute(&mut connection)
.await
.expect("error saving the new account");
}
let inserted_account_id = inserted_account.last_insert_rowid();
// user management
{
use db::models::{User, NewUser};
use db::schema::users::dsl::*;
let new_user = NewUser {
account_id: inserted_account.id,
username: input.userName.clone(),
registered: 1
};
diesel::insert_into(users)
.values(&new_user)
.get_result::<User, >(connection)
sqlx::query!("INSERT INTO users (account_id, username, registered) VALUES (?, ?, 1)", inserted_account_id, username)
.execute(&mut connection)
.await
.expect("error saving the new user");
}
return status::Custom(Status::Ok, "1")
return status::Custom(Status::Ok, "1");
}

View file

@ -1,62 +0,0 @@
use rocket::form::Form;
use rocket::http::Status;
use rocket::response::status;
use diesel::prelude::*;
use crate::helpers;
use crate::db;
#[derive(FromForm)]
pub struct FormUpdateAccountSettings {
accountID: i32,
mS: i32,
frS: i32,
cS: i32,
yt: String,
twitter: String,
twitch: String,
password: Option<String>,
gjp: Option<String>,
gjp2: Option<String>,
}
#[post("/updateGJAccSettings20.php", data = "<input>")]
pub fn update_account_settings(input: Form<FormUpdateAccountSettings>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg();
// account verification
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()) {
Ok((user_id, account_id)) => {
_user_id_val = user_id;
account_id_val = account_id;
},
Err(_) => return status::Custom(Status::Ok, "-1")
};
{
use db::models::Account;
use db::schema::accounts::dsl::*;
diesel::update(accounts)
.filter(id.eq(account_id_val))
.set((
messages_enabled.eq(input.mS.clamp(0, 2)),
friend_requests_enabled.eq(input.frS.clamp(0, 1)),
comments_enabled.eq(input.cS.clamp(0, 2)),
youtube_url.eq(input.yt.chars().take(20).collect::<String>()),
twitch_url.eq(input.twitch.chars().take(25).collect::<String>()),
twitter_url.eq(input.twitter.chars().take(15).collect::<String>())
))
.get_result::<Account, >(connection)
.expect("failed to update account");
}
return status::Custom(Status::Ok, "1")
}

View file

@ -2,8 +2,6 @@ use rocket::form::Form;
use rocket::http::Status;
use rocket::response::status;
use diesel::prelude::*;
use base64::{Engine as _, engine::general_purpose};
use flate2::read::{GzDecoder, ZlibDecoder};
@ -24,16 +22,11 @@ pub struct FormDownloadLevel {
}
#[post("/downloadGJLevel22.php", data = "<input>")]
pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg();
use db::schema::{levels, users};
use db::models::{Level, User};
pub async fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static str> {
let mut connection = db::establish_sqlite_conn().await;
let mut response: Vec<String> = Vec::new();
let query = levels::table.into_boxed();
match input.levelID {
-1 => {
unimplemented!("no daily support")
@ -49,23 +42,18 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
}
}
// database query
{
let result = query
.filter(levels::id.eq(input.levelID))
.get_result::<Level, >(connection)
.expect("fatal error loading levels");
let result = sqlx::query!("SELECT levels.id, levels.name, levels.extra_data, levels.level_info, levels.password, levels.user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, levels.created_at, levels.modified_at, users.username, users.udid, users.account_id, users.registered, editor_time, editor_time_copies FROM levels JOIN users ON levels.user_id = users.id WHERE levels.id = ?", input.levelID)
.fetch_one(&mut connection)
.await
.expect("error loading levels");
let user: User = users::table.find(result.user_id).get_result::<User, >(connection).expect("couldnt get user from lvl");
let level: Level = result;
let set_difficulty = match level.difficulty {
let set_difficulty = match result.difficulty {
Some(diff) => {
Some(helpers::difficulty::LevelDifficulty::new(diff))
},
None => None
};
let community_difficulty = match level.community_difficulty {
let community_difficulty = match result.community_difficulty {
Some(diff) => {
Some(helpers::difficulty::LevelDifficulty::new(diff))
},
@ -84,7 +72,7 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
}
}
};
let demon_difficulty = match level.demon_difficulty {
let demon_difficulty = match result.demon_difficulty {
Some(diff) => {
Some(helpers::difficulty::DemonDifficulty::new(diff))
},
@ -93,12 +81,12 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
let xor_pass: String;
if input.gameVersion.unwrap_or(19) >= 20 {
xor_pass = general_purpose::URL_SAFE.encode(helpers::encryption::cyclic_xor_string(&level.password.clone().unwrap_or(String::from("0")), "26364"))
xor_pass = general_purpose::URL_SAFE.encode(helpers::encryption::cyclic_xor_string(&result.password.clone().unwrap_or(String::from("0")), "26364"))
} else {
xor_pass = level.password.clone().unwrap_or(String::from("0"));
xor_pass = result.password.clone().unwrap_or(String::from("0"));
}
let compressed_level_data = fs::read(format!("{}/{}/{}.lvl", config::config_get_with_default("db.data_folder", "data"), "levels", level.id)).expect("couldnt read level file");
let compressed_level_data = fs::read(format!("{}/{}/{}.lvl", config::config_get_with_default("db.data_folder", "data".to_string()), "levels", result.id)).expect("couldnt read level file");
let uncompressed_level_data = String::from_utf8(if compressed_level_data.starts_with(&[0x1F, 0x8B]) {
// gzip!!
@ -119,16 +107,16 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
let level_data = uncompressed_level_data.as_bytes();
response.push(helpers::format::format(hashmap! {
1 => level.id.to_string(),
2 => level.name,
1 => result.id.to_string(),
2 => result.name,
3 => if input.gameVersion.unwrap_or(19) >= 20 {
general_purpose::URL_SAFE.encode(level.description)
general_purpose::URL_SAFE.encode(result.description)
} else {
level.description
result.description
},
4 => String::from_utf8(level_data.to_vec()).expect("invalid utf-8 sequence"),
5 => level.version.to_string(),
6 => user.id.to_string(),
5 => result.version.to_string(),
6 => result.user_id.to_string(),
// this argument is weird. its the "difficulty divisor"
// used to be vote count but yeah
8 => 10.to_string(),
@ -136,12 +124,12 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
Some(diff) => diff.to_star_difficulty(),
None => 0
} * 10).to_string(),
10 => level.downloads.to_string(),
12 => (if level.song_id < 50 { level.song_id } else { 0 }).to_string(),
13 => level.game_version.to_string(),
14 => level.likes.to_string(),
16 => (-level.likes).to_string(),
15 => level.length.to_string(),
10 => result.downloads.to_string(),
12 => (if result.song_id < 50 { result.song_id } else { 0 }).to_string(),
13 => result.game_version.to_string(),
14 => result.likes.to_string(),
16 => (-result.likes).to_string(),
15 => result.length.to_string(),
17 => match difficulty {
Some(diff) => {
if diff == helpers::difficulty::LevelDifficulty::Demon {
@ -152,8 +140,8 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
},
None => 0
}.to_string(),
18 => (if let Some(stars) = level.stars { stars } else { 0 }).to_string(),
19 => level.featured.to_string(),
18 => (if let Some(stars) = result.stars { stars } else { 0 }).to_string(),
19 => result.featured.to_string(),
25 => match difficulty {
Some(diff) => {
if diff == helpers::difficulty::LevelDifficulty::Auto {
@ -167,16 +155,16 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
27 => xor_pass,
28 => "1".to_string(), // unimplemented
29 => "1".to_string(), // unimplemented
30 => (if let Some(original) = level.original { original } else { 0 }).to_string(),
31 => level.two_player.to_string(),
35 => (if level.song_id >= 50 { level.song_id } else { 0 }).to_string(),
36 => String::from_utf8(if input.extras.is_some() { level.extra_data } else { Vec::new() }).expect("invalid utf-8 sequence"),
37 => level.coins.to_string(),
38 => level.rated_coins.to_string(),
39 => (if let Some(requested_stars) = level.requested_stars { requested_stars } else { 0 }).to_string(),
40 => level.has_ldm.to_string(),
30 => (if let Some(original) = result.original { original } else { 0 }).to_string(),
31 => result.two_player.to_string(),
35 => (if result.song_id >= 50 { result.song_id } else { 0 }).to_string(),
36 => String::from_utf8(if input.extras.is_some() { result.extra_data } else { Vec::new() }).expect("invalid utf-8 sequence"),
37 => result.coins.to_string(),
38 => result.rated_coins.to_string(),
39 => (if let Some(requested_stars) = result.requested_stars { requested_stars } else { 0 }).to_string(),
40 => result.has_ldm.to_string(),
41 => "".to_string(), // unimplemented
42 => level.epic.to_string(),
42 => result.epic.to_string(),
43 => match demon_difficulty {
Some(diff) => {
diff
@ -184,15 +172,15 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
None => helpers::difficulty::DemonDifficulty::Hard
}.to_demon_difficulty().to_string(),
44 => "0".to_string(), // unimplemented
45 => level.objects.to_string(),
46 => level.editor_time.to_string(),
47 => level.editor_time_copies.to_string()
45 => result.objects.to_string(),
46 => result.editor_time.to_string(),
47 => result.editor_time_copies.to_string()
}));
response.push(helpers::encryption::gen_solo(String::from_utf8(level_data.to_vec()).expect("invalid utf-8 sequence")));
let thing = [
user.id.to_string(),
level.stars.unwrap_or(0).to_string(),
result.user_id.to_string(),
result.stars.unwrap_or(0).to_string(),
match difficulty {
Some(diff) => {
if diff == helpers::difficulty::LevelDifficulty::Demon {
@ -203,14 +191,13 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static
},
None => 0
}.to_string(),
level.id.to_string(),
level.rated_coins.to_string(),
level.featured.to_string(),
level.password.unwrap_or(String::new()).to_string(),
result.id.to_string(),
result.rated_coins.to_string(),
result.featured.to_string(),
result.password.unwrap_or(String::new()).to_string(),
0.to_string()
];
response.push(helpers::encryption::gen_solo_2(thing.join(",")));
}
return status::Custom(Status::Ok, Box::leak(response.join("#").into_boxed_str()))
}

View file

@ -2,19 +2,53 @@ use rocket::form::Form;
use rocket::http::Status;
use rocket::response::status;
use diesel::prelude::*;
use base64::{Engine as _, engine::general_purpose};
use sqlx::Type;
use sqlx::{Encode, Sqlite, query_builder::QueryBuilder, Execute};
use crate::helpers;
use crate::db;
#[derive(Debug, sqlx::FromRow)]
struct DynQuery {
id: i64,
name: String,
user_id: i64,
description: String,
original: Option<i32>,
game_version: i32,
requested_stars: Option<i32>,
version: i32,
song_id: i32,
length: i32,
objects: i32,
coins: i32,
has_ldm: i32,
two_player: i32,
downloads: i32,
likes: i32,
difficulty: Option<i64>,
community_difficulty: Option<i64>,
demon_difficulty: Option<i64>,
stars: Option<i32>,
featured: i32,
epic: i32,
rated_coins: i32,
user_username: String,
user_udid: Option<String>,
user_account_id: Option<i64>,
user_registered: i32,
editor_time: i32,
editor_time_copies: i32
}
#[derive(FromForm)]
pub struct FormGetLevels {
page: i64,
str: String,
page: Option<i64>,
str: Option<String>,
accountID: Option<i32>,
accountID: Option<i64>,
gjp: Option<String>,
gjp2: Option<String>,
password: Option<String>,
@ -41,55 +75,57 @@ pub struct FormGetLevels {
}
#[post("/getGJLevels20.php", data = "<input>")]
pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg();
use db::schema::{levels, users};
use db::models::{Level, User};
pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
let mut connection = db::establish_sqlite_conn().await;
let mut can_see_unlisted = false;
let mut query = levels::table.into_boxed();
let mut count_query = levels::table.into_boxed();
// WHERE [...]
let mut query_params: Vec<&str> = vec![];
// Use this for binding on `query_params`
let mut query_params_bind: Vec<Box<dyn ToString + Send>> = vec![];
// ORDER BY [...]
let mut order = "levels.created_at DESC";
if input.str != "" && input.r#type != Some(5) && input.r#type != Some(10) && input.r#type != Some(19) {
match input.str.parse::<i32>() {
Ok(matched_id) => {
let page_offset = input.page.unwrap_or(0) * 10;
let search_query = input.str.clone().unwrap_or("".to_string());
if !search_query.is_empty() && input.r#type != Some(5) && input.r#type != Some(10) && input.r#type != Some(19) {
match search_query.parse::<i64>() {
Ok(id) => {
can_see_unlisted = true;
query = query.filter(levels::id.eq(matched_id));
count_query = count_query.filter(levels::id.eq(matched_id))
query_params.push("levels.id = ?");
query_params_bind.push(Box::new(id))
},
Err(_) => {
query = query.filter(levels::name.ilike(input.str.to_owned() + "%"));
count_query = count_query.filter(levels::name.ilike(input.str.to_owned() + "%"))
query_params.push("levels.name LIKE ?");
query_params_bind.push(Box::new(search_query.clone() + "%"));
}
}
}
if let Some(1) = input.featured {
query = query.filter(levels::featured.eq(1));
count_query = count_query.filter(levels::featured.eq(1))
query_params.push("featured = 1");
}
if let Some(1) = input.original {
query = query.filter(levels::original.is_null());
count_query = count_query.filter(levels::original.is_null())
query_params.push("original IS NULL");
}
if let Some(1) = input.coins {
query = query.filter(levels::rated_coins.eq(1).and(levels::coins.ne(0)));
count_query = count_query.filter(levels::rated_coins.eq(1).and(levels::coins.ne(0)))
query_params.push("rated_coins = 1 AND levels.coins != 0");
}
if let Some(1) = input.epic {
query = query.filter(levels::epic.eq(1));
count_query = count_query.filter(levels::epic.eq(1))
query_params.push("epic = 1");
}
if let Some(1) = input.uncompleted {
match input.completedLevels.clone() {
Some(completed_levels) => {
let clean_levels: Vec<i32> = completed_levels[1..completed_levels.len() - 1].split(',')
.map(|s| s.parse::<i32>().expect("failed to parse i32"))
let clean_levels: Vec<i64> = completed_levels[1..completed_levels.len() - 1].split(',')
.map(|s| s.parse::<i64>().expect("failed to parse i64"))
.collect();
query = query.filter(levels::id.ne_all(clean_levels.clone()));
count_query = count_query.filter(levels::id.ne_all(clean_levels))
let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
query_params.push("levels.id NOT IN (?)");
query_params_bind.push(Box::new(levels_str));
},
None => return status::Custom(Status::Ok, "-1")
}
@ -97,91 +133,92 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
if let Some(1) = input.onlyCompleted {
match input.completedLevels.clone() {
Some(completed_levels) => {
let clean_levels: Vec<i32> = completed_levels[1..completed_levels.len() - 1].split(',')
.map(|s| s.parse::<i32>().expect("failed to parse i32"))
let clean_levels: Vec<i64> = completed_levels[1..completed_levels.len() - 1].split(',')
.map(|s| s.parse::<i64>().expect("failed to parse i64"))
.collect();
query = query.filter(levels::id.eq_any(clean_levels.clone()));
count_query = count_query.filter(levels::id.eq_any(clean_levels))
let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
query_params.push("levels.id IN (?)");
query_params_bind.push(Box::new(levels_str));
},
None => return status::Custom(Status::Ok, "-1")
}
}
if let Some(song_id) = input.song {
if let Some(custom_song) = input.customSong {
query = query.filter(levels::song_id.eq(custom_song));
count_query = count_query.filter(levels::song_id.eq(custom_song))
query_params.push("song_id = ?");
query_params_bind.push(Box::new(custom_song));
} else {
query = query.filter(levels::song_id.eq(song_id));
count_query = count_query.filter(levels::song_id.eq(song_id));
query_params.push("song_id = ?");
query_params_bind.push(Box::new(song_id));
}
}
if let Some(1) = input.twoPlayer {
query = query.filter(levels::two_player.eq(1));
count_query = count_query.filter(levels::two_player.eq(1))
query_params.push("two_player = 1");
}
if let Some(1) = input.star {
query = query.filter(levels::stars.is_not_null());
count_query = count_query.filter(levels::stars.is_not_null())
query_params.push("levels.stars IS NOT NULL");
}
if let Some(1) = input.noStar {
query = query.filter(levels::stars.is_null());
count_query = count_query.filter(levels::stars.is_null())
query_params.push("levels.stars IS NULL");
}
if let Some(_gauntlet_id) = input.gauntlet {
unimplemented!("no gauntlet support")
}
if let Some(len) = input.len {
query = query.filter(levels::length.eq(len));
count_query = count_query.filter(levels::length.eq(len))
query_params.push("levels.length = ?");
query_params_bind.push(Box::new(len));
}
if let Some(diff) = input.diff.clone() {
if diff != "-" {
match diff.as_str() {
"-1" => {
query = query.filter(levels::difficulty.is_null().and(levels::community_difficulty.is_null()));
count_query = count_query.filter(levels::difficulty.is_null().and(levels::community_difficulty.is_null()))
query_params.push("difficulty IS NULL AND community_difficulty IS NULL"); // NA
},
"-2" => match input.demonFilter {
Some(demon_filter) => {
match demon_filter {
1 => {
query = query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Easy.to_demon_difficulty()));
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Easy.to_demon_difficulty()))
query_params.push("demon_difficulty = ?");
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Easy.to_demon_difficulty()));
},
2 => {
query = query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Medium.to_demon_difficulty()));
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Medium.to_demon_difficulty()))
query_params.push("demon_difficulty = ?");
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Medium.to_demon_difficulty()));
},
3 => {
query = query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Hard.to_demon_difficulty()));
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Hard.to_demon_difficulty()))
query_params.push("demon_difficulty = ?");
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Hard.to_demon_difficulty()));
},
4 => {
query = query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Insane.to_demon_difficulty()));
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Insane.to_demon_difficulty()))
query_params.push("demon_difficulty = ?");
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Insane.to_demon_difficulty()));
},
5 => {
query = query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Extreme.to_demon_difficulty()));
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(helpers::difficulty::DemonDifficulty::Extreme.to_demon_difficulty()))
query_params.push("demon_difficulty = ?");
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Extreme.to_demon_difficulty()));
},
_ => panic!("invalid demon filter!")
}
query = query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()))));
count_query = count_query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()))))
query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)");
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()));
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()));
},
None => panic!("demon filter option with no demon filter argument")
},
"-3" => {
query = query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()))));
count_query = count_query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()))))
query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)");
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()));
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()));
},
// easy, normal, hard, harder, insane
_ => {
let diffs: Vec<i32> = diff.split(',')
let clean_diffs: Vec<i32> = diff.split(',')
.map(|v| v.parse::<i32>().expect("couldnt parse i32"))
.collect();
query = query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs.clone()))));
count_query = count_query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs))))
let diffs_str = clean_diffs.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
query_params.push("difficulty IN (?) OR (difficulty IS NULL AND community_difficulty IN (?))");
query_params_bind.push(Box::new(diffs_str.clone()));
query_params_bind.push(Box::new(diffs_str));
}
}
}
@ -191,13 +228,11 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
match search_type {
// downloads
1 => {
query = query.order(levels::downloads.desc());
// count query order doesnt matter
order = "levels.downloads DESC";
},
// likes
2 => {
query = query.order(levels::likes.desc());
// count query order doesnt matter
order = "levels.likes DESC";
},
// trending
3 => {
@ -212,9 +247,9 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
5 => {
if let Some(1) = input.local {
if let Some(input_account_id) = input.accountID {
let (user_id_val, _account_id_val): (i32, i32);
let (user_id_val, _account_id_val): (i64, i64);
match helpers::accounts::auth(input_account_id, input.password.clone(), input.gjp.clone(), input.gjp2.clone()) {
match helpers::accounts::auth(input_account_id, input.password.clone(), input.gjp.clone(), input.gjp2.clone()).await {
Ok((user_id_val_auth, account_id_val_auth)) => {
user_id_val = user_id_val_auth;
_account_id_val = account_id_val_auth;
@ -222,37 +257,34 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
Err(_) => return status::Custom(Status::Ok, "-1")
};
if user_id_val == input.str.parse::<i32>().expect("couldnt convert query input to i32") {
if user_id_val == search_query.parse::<i64>().expect("couldnt convert query input to i64") {
can_see_unlisted = true;
query = query.filter(levels::user_id.eq(user_id_val));
count_query = count_query.filter(levels::user_id.eq(user_id_val))
query_params.push("levels.user_id = ?");
query_params_bind.push(Box::new(user_id_val));
} else {
return status::Custom(Status::Ok, "-1")
}
}
}
if let None = input.local {
let user_id_val = input.str.parse::<i32>().expect("couldnt convert query input to i32");
let user_id_val = search_query.parse::<i64>().expect("couldnt convert query input to i64");
query = query.filter(levels::user_id.eq(user_id_val));
count_query = count_query.filter(levels::user_id.eq(user_id_val))
query_params.push("levels.user_id = ?");
query_params_bind.push(Box::new(user_id_val));
}
}
// featured
// 17 is gdworld
6 | 17 => {
query = query.filter(levels::featured.eq(1));
count_query = count_query.filter(levels::featured.eq(1))
query_params.push("featured = 1");
},
// epic / HoF
16 => {
query = query.filter(levels::epic.eq(1));
count_query = count_query.filter(levels::epic.eq(1))
query_params.push("epic = 1");
},
// magic
7 => {
query = query.filter(levels::objects.gt(4000));
count_query = count_query.filter(levels::objects.gt(4000))
query_params.push("objects > 4000");
},
// map packs 🙄😶
10 | 19 => {
@ -260,8 +292,7 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
},
// rated
11 => {
query = query.filter(levels::stars.is_not_null());
count_query = count_query.filter(levels::stars.is_not_null())
query_params.push("levels.stars IS NOT NULL");
},
// followed
12 => {
@ -286,72 +317,63 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
// default sort
// 15 is gdworld
0 | 15 | _ => {
query = query.order(levels::likes.desc());
// count query order doesnt matter
order = "likes DESC";
},
}
}
if !can_see_unlisted {
query = query.filter(levels::unlisted.eq(0));
count_query = count_query.filter(levels::unlisted.eq(0))
query_params.push("unlisted = 0");
}
let mut results: Vec<String> = [].to_vec();
let mut users: Vec<String> = [].to_vec();
let mut songs: Vec<String> = [].to_vec();
let where_str = format!("WHERE ({})", query_params.join(" AND "));
let query_base = format!("FROM levels JOIN users ON levels.user_id = users.id {} ORDER BY {}", where_str, order);
let mut hash_data: Vec<(i32, i32, bool)> = [].to_vec();
let mut query_builder: QueryBuilder<Sqlite> = QueryBuilder::new(&format!("SELECT levels.id, levels.name, users.id as user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username as user_username, users.udid as user_udid, users.account_id as user_account_id, users.registered as user_registered, editor_time, editor_time_copies {}", query_base));
let mut query = query_builder.build_query_as::<DynQuery>();
for param in query_params_bind {
query = query.bind(param.to_string());
}
let mut results: Vec<String> = vec![];
let mut users: Vec<String> = vec![];
let mut songs: Vec<String> = vec![];
let mut hash_data: Vec<(i64, i32, bool)> = vec![];
let count: i64 = sqlx::query_scalar(&format!("SELECT COUNT(*) {}", query_base))
.fetch_one(&mut connection)
.await
.expect("error getting level count");
// for goop in {
// query
// .fetch_all(&mut connection)
// .await
// .expect("error loading levels")
// } {
// println!("lvl id: {:?}", goop.id);
// println!("user id: {:?}", goop.user_id);
// }
for result in {
query
.order(levels::created_at.desc())
.offset(input.page * 10)
.limit(10)
.get_results::<Level, >(connection)
.expect("fatal error loading levels")
.fetch_all(&mut connection)
.await
.expect("error loading levels")
} {
let user: User = users::table.find(result.user_id).get_result::<User, >(connection).expect("couldnt get user from lvl");
let level: Level = result;
let set_difficulty = match level.difficulty {
Some(diff) => {
Some(helpers::difficulty::LevelDifficulty::new(diff))
},
None => None
};
let community_difficulty = match level.community_difficulty {
Some(diff) => {
Some(helpers::difficulty::LevelDifficulty::new(diff))
},
None => None
};
let difficulty = match set_difficulty {
Some(diff) => {
Some(diff)
},
None => {
match community_difficulty {
Some(diff) => {
Some(diff)
},
None => None
}
}
};
let demon_difficulty = match level.demon_difficulty {
Some(diff) => {
Some(helpers::difficulty::DemonDifficulty::new(diff))
},
None => None
};
let set_difficulty = result.difficulty.map(helpers::difficulty::LevelDifficulty::new);
let community_difficulty = result.community_difficulty.map(helpers::difficulty::LevelDifficulty::new);
let difficulty = set_difficulty.or(community_difficulty);
let demon_difficulty = result.demon_difficulty.map(helpers::difficulty::DemonDifficulty::new);
results.push(helpers::format::format(hashmap! {
1 => level.id.to_string(),
2 => level.name,
3 => general_purpose::URL_SAFE.encode(level.description),
5 => level.version.to_string(),
6 => user.id.to_string(),
1 => result.id.to_string(),
2 => result.name,
3 => general_purpose::URL_SAFE.encode(result.description),
5 => result.version.to_string(),
6 => result.user_id.to_string(),
// this argument is weird. its the "difficulty divisor"
// used to be vote count but yeah
8 => 10.to_string(),
@ -359,12 +381,12 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
Some(diff) => diff.to_star_difficulty(),
None => 0
} * 10).to_string(),
10 => level.downloads.to_string(),
12 => (if level.song_id < 50 { level.song_id } else { 0 }).to_string(),
13 => level.game_version.to_string(),
14 => level.likes.to_string(),
16 => (-level.likes).to_string(),
15 => level.length.to_string(),
10 => result.downloads.to_string(),
12 => (if result.song_id < 50 { result.song_id } else { 0 }).to_string(),
13 => result.game_version.to_string(),
14 => result.likes.to_string(),
16 => (-result.likes).to_string(),
15 => result.length.to_string(),
17 => match difficulty {
Some(diff) => {
if diff == helpers::difficulty::LevelDifficulty::Demon {
@ -375,8 +397,8 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
},
None => 0
}.to_string(),
18 => (if let Some(stars) = level.stars { stars } else { 0 }).to_string(),
19 => level.featured.to_string(),
18 => (if let Some(stars) = result.stars { stars } else { 0 }).to_string(),
19 => result.featured.to_string(),
25 => match difficulty {
Some(diff) => {
if diff == helpers::difficulty::LevelDifficulty::Auto {
@ -387,54 +409,49 @@ pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
},
None => 0
}.to_string(),
30 => (if let Some(original) = level.original { original } else { 0 }).to_string(),
31 => level.two_player.to_string(),
35 => (if level.song_id >= 50 { level.song_id } else { 0 }).to_string(),
37 => level.coins.to_string(),
38 => level.rated_coins.to_string(),
39 => (if let Some(requested_stars) = level.requested_stars { requested_stars } else { 0 }).to_string(),
40 => level.has_ldm.to_string(),
42 => level.epic.to_string(),
30 => (if let Some(original) = result.original { original } else { 0 }).to_string(),
31 => result.two_player.to_string(),
35 => (if result.song_id >= 50 { result.song_id } else { 0 }).to_string(),
37 => result.coins.to_string(),
38 => result.rated_coins.to_string(),
39 => (if let Some(requested_stars) = result.requested_stars { requested_stars } else { 0 }).to_string(),
40 => result.has_ldm.to_string(),
42 => result.epic.to_string(),
43 => match demon_difficulty {
Some(diff) => {
diff
},
None => helpers::difficulty::DemonDifficulty::Hard
}.to_demon_difficulty().to_string(),
45 => level.objects.to_string(),
46 => level.editor_time.to_string(),
47 => level.editor_time_copies.to_string()
45 => result.objects.to_string(),
46 => result.editor_time.to_string(),
47 => result.editor_time_copies.to_string()
}));
users.push(format!("{}:{}:{}", user.id, user.username, {
if user.registered == 1 {
user.account_id.expect("wtf? registered user with no account id.").to_string()
users.push(format!("{}:{}:{}", result.user_id, result.user_username, {
if result.user_registered == 1 {
result.user_account_id.expect("wtf? registered user with no account id.").to_string()
} else {
user.udid.expect("wtf? unregistered user with no udid.")
result.user_udid.expect("wtf? unregistered user with no udid.")
}
}));
hash_data.push((
level.id,
{ if let Some(stars) = level.stars {
result.id,
{ if let Some(stars) = result.stars {
stars
} else {
0
}},
{ if let 1 = level.rated_coins {
{ if let 1 = result.rated_coins {
true
} else {
false
}}
));
};
}
let level_count = count_query
.count()
.get_result::<i64, >(connection)
.expect("failed to get count of levels");
let search_meta = format!("{}:{}:{}", level_count, input.page * 10, 10);
let search_meta = format!("{}:{}:{}", count, page_offset, 10);
let response = vec![results.join("|"), users.join("|"), songs.join("|"), search_meta, helpers::encryption::gen_multi(hash_data)].join("#");

View file

@ -2,8 +2,6 @@ use rocket::form::Form;
use rocket::http::Status;
use rocket::response::status;
use diesel::prelude::*;
use base64::{Engine as _, engine::general_purpose};
use std::fs;
@ -14,7 +12,7 @@ use crate::db;
#[derive(FromForm)]
pub struct FormUploadLevel {
accountID: i32,
accountID: i64,
gjp: Option<String>,
gjp2: Option<String>,
@ -24,7 +22,7 @@ pub struct FormUploadLevel {
audioTrack: i32,
levelName: String,
levelDesc: String,
levelID: i32,
levelID: i64,
levelVersion: i32,
levelInfo: String,
levelString: String,
@ -40,14 +38,14 @@ pub struct FormUploadLevel {
}
#[post("/uploadGJLevel21.php", data = "<input>")]
pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg();
pub async fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str> {
let mut connection = db::establish_sqlite_conn().await;
// account verification
let (user_id_val, _account_id_val): (i32, i32);
let (user_id_val, _account_id_val): (i64, i64);
// password argument is used for the level, so
match helpers::accounts::auth(input.accountID.clone(), None, input.gjp.clone(), input.gjp2.clone()) {
match helpers::accounts::auth(input.accountID.clone(), None, input.gjp.clone(), input.gjp2.clone()).await {
Ok((user_id, account_id)) => {
user_id_val = user_id;
_account_id_val = account_id;
@ -98,105 +96,86 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
}
// too many objects
if config::config_get_with_default("levels.max_objects", 0) != 0 && objects_val > config::config_get_with_default("levels.max_objects", 0) {
let max_objects = config::config_get_with_default("levels.max_objects", 0) as usize;
if max_objects != 0 && objects_val > max_objects {
return status::Custom(Status::Ok, "-1")
}
// forbidden object checking
if let Some(_forbidden_object) = level_objects.iter().find(|obj| config::config_get_with_default("levels.blocklist", Vec::new() as Vec<i32>).contains(&obj.id())) {
if let Some(_obj) = level_objects.iter().find(|obj| config::config_get_with_default("levels.blocklist", Vec::new() as Vec<i32>).contains(&obj.id())) {
return status::Custom(Status::Ok, "-1")
}
// ACE vulnerability check
if let Some(_ace_object) = level_objects.iter().find(|obj| obj.item_block_id() < Some(0) || obj.item_block_id() > Some(1100)) {
return status::Custom(Status::Ok, "-1")
for obj in level_objects.iter().filter(|obj| obj.item_block_id().is_some()) {
if obj.item_block_id() < Some(0) || obj.item_block_id() > Some(1100) {
return status::Custom(Status::Ok, "-1");
}
}
// data base 🤣😁
{
use db::models::{Level, NewLevel};
use db::schema::levels::dsl::*;
if levels
.filter(id.eq(input.levelID))
.count()
.get_result::<i64>(connection)
.expect("couldnt get count of levels") > 0 {
if sqlx::query_scalar!("SELECT COUNT(*) FROM levels WHERE id = ?", input.levelID)
.fetch_one(&mut connection)
.await
.expect("error getting level count") > 0 {
// update level
let level_user_id = levels
.filter(id.eq(input.levelID))
.select(user_id)
.get_result::<i32>(connection)
.expect("couldnt query levels");
let level_user_id = sqlx::query!("SELECT user_id FROM levels WHERE id = ?", input.levelID)
.fetch_one(&mut connection)
.await
.expect("error getting level user id")
.user_id;
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(level_length_val),
objects.eq(objects_val as i32),
coins.eq(coins_val as i32),
has_ldm.eq(input.ldm.unwrap_or(0).clamp(0, 1)),
two_player.eq(two_player_val)
))
.get_result::<Level, >(connection)
.expect("failed to update level");
let new_description = description_val.chars().take(140).collect::<String>();
let new_password = input.password.clone();
let new_requested_stars = match input.requestedStars { Some(requested_stars_val) => requested_stars_val.clamp(0, 10), None => 0 };
let new_extra_string = extra_string.as_bytes().to_owned();
let new_level_info = input.levelInfo.clone().into_bytes();
let new_editor_time = input.wt.unwrap_or(0);
let new_editor_time_copies = input.wt2.unwrap_or(0);
let new_objects = objects_val as i64;
let new_coins = coins_val as i64;
let new_ldm = input.ldm.unwrap_or(0).clamp(0, 1);
fs::write(format!("{}/levels/{}.lvl", config::config_get_with_default("db.data_folder", "data"), updated_level.id), general_purpose::URL_SAFE.decode(input.levelString.clone()).expect("user provided invalid level string")).expect("couldnt write level to file");
let updated_level = sqlx::query!("UPDATE levels SET description = ?, password = ?, requested_stars = ?, version = ?, extra_data = ?, level_info = ?, editor_time = ?, editor_time_copies = ?, song_id = ?, length = ?, objects = ?, coins = ?, has_ldm = ?, two_player = ? WHERE id = ?",new_description, new_password, new_requested_stars, input.levelVersion, new_extra_string, new_level_info, new_editor_time, new_editor_time_copies, song_id_val, level_length_val, new_objects, new_coins, new_ldm, two_player_val, input.levelID)
.execute(&mut connection)
.await
.expect("error updating level");
let updated_level_id = updated_level.last_insert_rowid();
fs::write(format!("{}/levels/{}.lvl", config::config_get_with_default("db.data_folder", "data".to_string()), 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,
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).clamp(0, 1),
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: level_length_val,
objects: objects_val as i32,
coins: coins_val as i32,
has_ldm: input.ldm.unwrap_or(0).clamp(0, 1),
two_player: two_player_val
};
// insert level
let inserted_level = diesel::insert_into(levels)
.values(&new_level)
.get_result::<Level, >(connection)
.expect("failed to insert level");
let new_name = helpers::clean::clean_basic(&input.levelName).chars().take(20).collect::<String>();
let new_description = description_val.chars().take(140).collect::<String>();
let new_binary_version = input.binaryVersion.unwrap_or(0);
let new_password = input.password.clone();
let new_requested_stars = match input.requestedStars { Some(requested_stars_val) => requested_stars_val.clamp(0, 10), None => 0 };
let new_unlisted = input.unlisted.unwrap_or(0).clamp(0, 1);
let new_extra_string = extra_string.as_bytes().to_owned();
let new_level_info = input.levelInfo.clone().into_bytes();
let new_editor_time = input.wt.unwrap_or(0);
let new_editor_time_copies = input.wt2.unwrap_or(0);
let new_objects = objects_val as i64;
let new_coins = coins_val as i64;
let new_ldm = input.ldm.unwrap_or(0).clamp(0, 1);
fs::write(format!("{}/levels/{}.lvl", config::config_get_with_default("db.data_folder", "data"), inserted_level.id), general_purpose::URL_SAFE.decode(input.levelString.clone()).expect("user provided invalid level string")).expect("couldnt write level to file");
let inserted_level = sqlx::query!("INSERT INTO levels (name, user_id, description, original, game_version, binary_version, password, requested_stars, unlisted, version, extra_data, level_info, editor_time, editor_time_copies, song_id, length, objects, coins, has_ldm, two_player) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new_name, user_id_val, new_description, input.original, input.gameVersion, new_binary_version, new_password, new_requested_stars, new_unlisted, input.levelVersion, new_extra_string, new_level_info, new_editor_time, new_editor_time_copies, song_id_val, level_length_val, new_objects, new_coins, new_ldm, two_player_val)
.execute(&mut connection)
.await
.expect("error inserting level");
return status::Custom(Status::Ok, "1")
}
let inserted_level_id = inserted_level.last_insert_rowid();
fs::write(format!("{}/levels/{}.lvl", config::config_get_with_default("db.data_folder", "data".to_string()), 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, Box::leak(inserted_level_id.to_string().into_boxed_str()))
}
}

View file

@ -2,8 +2,6 @@ use rocket::form::Form;
use rocket::http::Status;
use rocket::response::status;
use diesel::prelude::*;
use crate::helpers;
use crate::db;
@ -14,31 +12,21 @@ pub struct FormGetUsers {
}
#[post("/getGJUsers20.php", data = "<input>")]
pub fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str> {
let connection = &mut db::establish_connection_pg();
pub async fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str> {
let mut connection = db::establish_sqlite_conn().await;
// query users
use db::schema::users::dsl::*;
use db::models::User;
let username = input.str.to_owned() + "%";
let offset = input.page * 10;
let mut query_users = users.into_boxed();
match input.str.parse::<i32>() {
Ok(id_value) => query_users = query_users.filter(id.eq(id_value)),
Err(_) => query_users = query_users.filter(username.ilike(input.str.to_owned() + "%"))
};
let query_results = sqlx::query!("SELECT * FROM users WHERE id = ? OR username LIKE ? ORDER BY stars DESC LIMIT 10 OFFSET ?", input.str, username, offset)
.fetch_all(&mut connection)
.await
.expect("Fatal error loading users");
let mut results: Vec<String> = vec![];
for result in {
query_users
.order(stars.desc())
.offset(input.page * 10)
.limit(10)
.get_results::<User, >(connection)
.expect("Fatal error loading users")
} {
let user: User = result;
for result in query_results {
let user = result;
let formatted_result = helpers::format::format(hashmap! {
1 => user.username,
@ -75,22 +63,15 @@ pub fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str> {
results.push(formatted_result)
};
let mut query_users_count = users.into_boxed();
match input.str.parse::<i32>() {
Ok(id_value) => query_users_count = query_users_count.filter(id.eq(id_value)),
Err(_) => query_users_count = query_users_count.filter(username.ilike(input.str.to_owned() + "%"))
};
let amount = query_users_count
.count()
.get_result::<i64>(connection)
.expect("error querying user count");
let amount = sqlx::query_scalar!("SELECT COUNT(*) FROM users WHERE id = ? OR username LIKE ?", input.str, username)
.fetch_one(&mut connection)
.await
.expect("error loading users");
let response = if results.is_empty() {
String::from("-1")
} else {
vec![results.join("|"), format!("{}:{}:10", amount, input.page * 10)].join("#")
vec![results.join("|"), format!("{}:{}:10", amount, offset)].join("#")
};
return status::Custom(Status::Ok, Box::leak(response.into_boxed_str()))

View file

@ -1,7 +1,7 @@
use diesel::prelude::*;
use password_auth::verify_password;
use crate::{db, helpers};
use crate::db;
use crate::helpers;
// returns userid, accountid
pub enum AuthError {
@ -9,39 +9,36 @@ pub enum AuthError {
AccountNotFound
}
pub fn auth(account_id: i32, password_val: Option<String>, gjp_val: Option<String>, gjp2_val: Option<String>) -> Result<(i32, i32), AuthError> {
use db::schema::accounts::dsl::*;
pub async fn auth(account_id: i64, password_val: Option<String>, gjp_val: Option<String>, gjp2_val: Option<String>) -> Result<(i64, i64), AuthError> {
let connection = &mut db::establish_sqlite_conn().await;
let connection = &mut db::establish_connection_pg();
let query_result = accounts
.select((password, gjp2))
.filter(id.eq(account_id))
.get_result::<(String, String)>(connection);
let query_result = sqlx::query!("SELECT password, gjp2 FROM accounts WHERE id = ?", account_id)
.fetch_one(connection)
.await;
match query_result {
Ok((
password_queried_val,
gjp2_queried_val
)) => {
Ok(result) => {
let password_queried_val = result.password;
let gjp2_queried_val = result.gjp2;
match password_val {
Some(password_val) => {
match verify_password(password_val, &password_queried_val) {
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
Err(_) => return Err(AuthError::WrongPassword)
}
},
None => match gjp_val {
Some(gjp_val) => {
match verify_password(helpers::encryption::decode_gjp(gjp_val), &password_queried_val) {
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
Err(_) => return Err(AuthError::WrongPassword)
}
},
None => match gjp2_val {
Some(gjp2_val) => {
match verify_password(gjp2_val, &gjp2_queried_val) {
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
Err(_) => return Err(AuthError::WrongPassword)
}
},
@ -56,16 +53,14 @@ pub fn auth(account_id: i32, password_val: Option<String>, gjp_val: Option<Strin
}
}
pub fn get_user_id_from_account_id(ext_id: i32) -> i32 {
use db::schema::users::dsl::*;
pub async fn get_user_id_from_account_id(ext_id: i64) -> i64 {
let connection = &mut db::establish_sqlite_conn().await;
let connection = &mut db::establish_connection_pg();
let user_id = users
.filter(udid.eq(ext_id.to_string()).or(account_id.eq(ext_id)))
.select(id)
.get_result::<i32>(connection)
.expect("No user associated with account?!?!?");
let user_id = sqlx::query!("SELECT id FROM users WHERE account_id = ?", ext_id)
.fetch_one(connection)
.await
.expect("no user associated with account id??")
.id;
return user_id
}

View file

@ -9,3 +9,8 @@ pub fn clean_basic(string: &str) -> String {
let regex = Regex::new(r"[^A-Za-z0-9\-_ ]").unwrap();
return regex.replace_all(string, "").to_string();
}
pub fn clean_char(string: &str) -> String {
let regex = Regex::new(r"[^A-Za-z0-9 ]").unwrap();
return regex.replace_all(string, "").to_string();
}

View file

@ -10,7 +10,7 @@ pub enum LevelDifficulty {
}
impl LevelDifficulty {
pub fn new(value: i32) -> LevelDifficulty {
pub fn new(value: i64) -> LevelDifficulty {
match value {
0 => LevelDifficulty::Auto,
1 => LevelDifficulty::Easy,
@ -23,7 +23,7 @@ impl LevelDifficulty {
}
}
pub fn value(self) -> i32 {
pub fn value(self) -> i64 {
match self {
LevelDifficulty::Auto => 0,
LevelDifficulty::Easy => 1,
@ -35,7 +35,7 @@ impl LevelDifficulty {
}
}
pub fn to_star_difficulty(self) -> i32 {
pub fn to_star_difficulty(self) -> i64 {
match self {
LevelDifficulty::Auto => 5,
LevelDifficulty::Easy => 1,
@ -47,7 +47,7 @@ impl LevelDifficulty {
}
}
pub fn stars_to_diff(stars: i32) -> Self {
pub fn stars_to_diff(stars: i64) -> Self {
match stars {
1 => LevelDifficulty::Auto,
2 => LevelDifficulty::Easy,
@ -70,7 +70,7 @@ pub enum DemonDifficulty {
}
impl DemonDifficulty {
pub fn new(value: i32) -> DemonDifficulty {
pub fn new(value: i64) -> DemonDifficulty {
match value {
0 => DemonDifficulty::Easy,
1 => DemonDifficulty::Medium,
@ -81,7 +81,7 @@ impl DemonDifficulty {
}
}
pub fn value(self) -> i32 {
pub fn value(self) -> i64 {
match self {
DemonDifficulty::Easy => 0,
DemonDifficulty::Medium => 1,
@ -91,7 +91,7 @@ impl DemonDifficulty {
}
}
pub fn to_demon_difficulty(self) -> i32 {
pub fn to_demon_difficulty(self) -> i64 {
match self {
DemonDifficulty::Easy => 3,
DemonDifficulty::Medium => 4,

View file

@ -29,7 +29,7 @@ pub fn decode_gjp(gjp: String) -> String {
return xor_decoded
}
pub fn gen_multi(level_hash_data: Vec<(i32, i32, bool)>) -> String {
pub fn gen_multi(level_hash_data: Vec<(i64, i32, bool)>) -> String {
let mut input_str = String::new();
for (_index, val) in level_hash_data.iter().enumerate() {

View file

@ -1,54 +1,48 @@
use std::sync::RwLock;
use diesel::prelude::*;
use crate::db;
pub const REUPLOAD_USER_NAME: &str = "reupload";
pub static REUPLOAD_ACCOUNT_ID: RwLock<i32> = RwLock::new(0);
pub static REUPLOAD_ACCOUNT_ID: RwLock<i64> = RwLock::new(0);
pub fn init() {
let connection = &mut db::establish_connection_pg();
pub async fn init() {
let mut connection = db::establish_sqlite_conn().await;
use db::schema::{accounts, users};
use db::models::{Account, NewAccount, User, NewUser};
let result = sqlx::query!("SELECT id FROM accounts WHERE username = ?", REUPLOAD_USER_NAME)
.fetch_one(&mut connection)
.await;
match accounts::table
.filter(accounts::username.eq(REUPLOAD_USER_NAME))
.select(accounts::id)
.get_result::<i32, >(connection) {
Ok(reupload_acc_id) => {
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock!!");
*write_lock = reupload_acc_id;
match result {
Ok(result) => {
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock");
*write_lock = result.id;
},
Err(_) => {
let new_account = NewAccount {
username: REUPLOAD_USER_NAME.to_string(),
gjp2: "!".to_string(),
password: "!".to_string(),
email: "".to_string()
};
let inserted_account = diesel::insert_into(accounts::table)
.values(&new_account)
.get_result::<Account, >(connection)
let new_account = sqlx::query!(
"INSERT INTO accounts (username, gjp2, password, email) VALUES (?, ?, ?, ?)",
REUPLOAD_USER_NAME,
"!",
"!",
""
)
.execute(&mut connection)
.await
.expect("error saving the new account");
let reupload_acc_id = inserted_account.id;
let reupload_acc_id = new_account.last_insert_rowid() as i64;
let new_user = NewUser {
account_id: inserted_account.id,
username: REUPLOAD_USER_NAME.to_string(),
registered: 1
};
diesel::insert_into(users::table)
.values(&new_user)
.get_result::<User, >(connection)
sqlx::query!(
"INSERT INTO users (account_id, username, registered) VALUES (?, ?, ?)",
reupload_acc_id,
REUPLOAD_USER_NAME,
1
)
.execute(&mut connection)
.await
.expect("error saving the new user");
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock!!");
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock");
*write_lock = reupload_acc_id;
println!("created reupload account, id: {}", reupload_acc_id);

View file

@ -12,11 +12,11 @@ use rocket::data::{Limits, ToByteUnit};
use rocket_dyn_templates::Template;
mod db;
mod helpers;
mod endpoints;
mod template_endpoints;
mod config;
mod db;
mod endpoints;
mod helpers;
mod template_endpoints;
#[get("/<file..>")]
async fn files(file: PathBuf) -> Option<NamedFile> {
@ -24,14 +24,14 @@ async fn files(file: PathBuf) -> Option<NamedFile> {
}
#[launch]
fn rocket() -> _ {
async fn rocket() -> _ {
// init stuff
crate::helpers::reupload::init();
crate::helpers::reupload::init().await;
// data directories
// unhardcore this maybe?
fs::create_dir_all(config::config_get_with_default("db.data_folder", "data")).expect("failed to create data directory!");
fs::create_dir_all(format!("{}/levels", config::config_get_with_default("db.data_folder", "data"))).expect("failed to create data directory for levels");
fs::create_dir_all(config::config_get_with_default("db.data_folder", "data".to_string())).expect("failed to create data directory!");
fs::create_dir_all(format!("{}/levels", config::config_get_with_default("db.data_folder", "data".to_string()))).expect("failed to create data directory for levels");
rocket::build()
// conf
@ -57,10 +57,9 @@ fn rocket() -> _ {
files
])
// https://www.youtube.com/watch?v=_pLrtsf5yfE
.mount(config::config_get_with_default("general.append_path", "/"), routes![
.mount(format!("/{}", config::config_get_with_default("general.append_path", "".to_string())), routes![
endpoints::accounts::login_account::login_account,
endpoints::accounts::register_account::register_account,
endpoints::accounts::update_account_settings::update_account_settings,
endpoints::users::get_users::get_users,
@ -68,6 +67,6 @@ fn rocket() -> _ {
endpoints::levels::get_levels::get_levels,
endpoints::levels::upload_level::upload_level
])
// so templates work i think
// so templates work
.attach(Template::fairing())
}

View file

@ -4,25 +4,20 @@ use rocket_dyn_templates::{Template, context};
use rocket::http::CookieJar;
use diesel::prelude::*;
use crate::db;
#[get("/accounts")]
pub fn account_management(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
let connection = &mut db::establish_connection_pg();
pub async fn account_management(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
let connection = &mut db::establish_sqlite_conn().await;
let logged_in = crate::helpers::templates::authenticate(cookies);
match logged_in {
Ok((username_val, _account_id_val, user_id_val)) => {
use db::schema::users::dsl::*;
use db::models::User;
let result = users
.filter(id.eq(user_id_val))
.get_result::<User, >(connection)
.expect("couldnt find user with user id from account");
Ok((username_val, _account_id, user_id)) => {
let result = sqlx::query!("SELECT stars, demons, coins, user_coins, diamonds, creator_points FROM users WHERE id = ?", user_id)
.fetch_one(connection)
.await
.expect("couldnt query database");
return Ok(Template::render("account_management", context! {
username: username_val,

View file

@ -8,8 +8,6 @@ use rocket::http::{Cookie, CookieJar};
use rocket::time::Duration;
use diesel::prelude::*;
use crate::db;
use crate::helpers;
@ -20,23 +18,22 @@ pub struct FormLogin {
}
#[post("/login", data = "<input>")]
pub fn post_login(cookies: &CookieJar<'_>, input: Form<FormLogin>) -> Template {
let connection = &mut db::establish_connection_pg();
pub async fn post_login(cookies: &CookieJar<'_>, input: Form<FormLogin>) -> Template {
let connection = &mut db::establish_sqlite_conn().await;
use db::schema::accounts::dsl::*;
let result = accounts
.select((id, username))
.filter(username.eq(input.username.clone()))
.get_result::<(i32, String), >(connection);
let result = sqlx::query!("SELECT id, username FROM accounts WHERE username = ?", input.username)
.fetch_one(connection)
.await;
match result {
Ok(account_id_username_val) => {
match helpers::accounts::auth(account_id_username_val.0, Some(input.password.clone()), None, None) {
Ok(result) => {
let account_username = result.username;
match helpers::accounts::auth(result.id, Some(input.password.clone()), None, None).await {
Ok(account_id_user_id_val) => {
cookies.add_private(Cookie::build(
"blackmail_data",
format!("{}:{}:{}", account_id_username_val.1, account_id_user_id_val.0, account_id_user_id_val.1))
format!("{}:{}:{}", account_username, result.id, account_id_user_id_val.1))
.path("/")
.secure(false)
.http_only(true)

View file

@ -12,8 +12,6 @@ use std::fs;
use base64::{Engine as _, engine::general_purpose};
use diesel::prelude::*;
use crate::helpers;
use crate::db;
use crate::config;
@ -38,7 +36,7 @@ pub struct FormReupload {
#[post("/tools/reupload", data = "<input>")]
pub async fn post_reupload(input: Form<FormReupload>) -> Template {
let connection = &mut db::establish_connection_pg();
let connection = &mut db::establish_sqlite_conn().await;
let disabled = !config::config_get_with_default("levels.reupload", true);
@ -71,45 +69,63 @@ pub async fn post_reupload(input: Form<FormReupload>) -> Template {
let gmd_file = reqwest::get(format!("https://history.geometrydash.eu/level/{}/{}/download/", remote_level_id, level.id)).await.expect("failed to fetch gmd file from remote server");
let level_data = helpers::levels::gmd_parse(&gmd_file.text().await.expect("failed to parse gmd file as text"));
use db::schema::levels::dsl::*;
use db::models::{Level, NewLevel};
let new_level = NewLevel {
name: level_data.get("k2").expect("level name not found").to_string(),
user_id: crate::helpers::reupload::REUPLOAD_ACCOUNT_ID.read().expect("poisoned lock!!").to_string().parse::<i32>().expect("reupload account id not int (shouldnt ever happen)"),
description: String::from_utf8(general_purpose::URL_SAFE.decode(general_purpose::URL_SAFE.decode(level_data.get("k3").expect("level description not found")).expect("couldnt decode base64")).expect("couldnt decode base64")).expect("invalid utf-8 sequence (how)"),
original: None,
game_version: level_data.get("k17").expect("level game version not found").to_string().parse::<i32>().expect("level game version not int"),
binary_version: level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::<i32>().expect("level binary version not int"),
password: Some(level_data.get("k41").expect("level password not found").to_string()),
requested_stars: level_data.get("k66").expect("level requested stars not found").to_string().parse::<i32>().expect("level requested stars not int"),
unlisted: 0,
version: level_data.get("k16").expect("level version not found").to_string().parse::<i32>().expect("level version not int"),
extra_data: level_data.get("extra_string").unwrap_or(&crate::helpers::levels::DEFAULT_EXTRA_STRING).to_string().into_bytes(),
level_info: crate::helpers::levels::DEFAULT_LEVEL_INFO.to_string().into_bytes(),
editor_time: level_data.get("k80").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time not int"),
editor_time_copies: level_data.get("k81").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time copies not int"),
song_id: if level_data.get("k8").unwrap_or(&String::from("0")).parse::<i32>().expect("level song id not int") == 0 {
let level_name = level_data.get("k2").expect("level name not found").to_string();
let reupload_account_id = helpers::reupload::REUPLOAD_ACCOUNT_ID.read().expect("poisoned lock!!").to_string().parse::<i32>().expect("reupload account id not int (shouldnt ever happen)");
let level_description = String::from_utf8(general_purpose::URL_SAFE.decode(general_purpose::URL_SAFE.decode(level_data.get("k3").expect("level description not found")).expect("couldnt decode base64")).expect("couldnt decode base64")).expect("invalid utf-8 sequence (how)");
let level_game_version = level_data.get("k17").expect("level game version not found").to_string().parse::<i32>().expect("level game version not int");
let level_binary_version = level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::<i32>().expect("level binary version not int");
let level_password = level_data.get("k41").expect("level password not found").to_string();
let level_requested_stars = level_data.get("k66").expect("level requested stars not found").to_string().parse::<i32>().expect("level requested stars not int");
let level_version = level_data.get("k16").expect("level version not found").to_string().parse::<i32>().expect("level version not int");
let extra_string = level_data.get("extra_string").unwrap_or(&crate::helpers::levels::DEFAULT_EXTRA_STRING).to_string().into_bytes();
let default_level_info = crate::helpers::levels::DEFAULT_LEVEL_INFO.to_string().into_bytes();
let level_editor_time = level_data.get("k80").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time not int");
let level_editor_time_copies = level_data.get("k81").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time copies not int");
let level_song_id = if level_data.get("k8").unwrap_or(&String::from("0")).parse::<i32>().expect("level song id not int") == 0 {
level_data.get("k45").expect("level song id doesnt fucking exist").parse::<i32>().expect("level song id not int")
} else {
level_data.get("k8").expect("level song id doesnt fucking exist").parse::<i32>().expect("level song id not int")
},
length: level.length.expect("level length doesnt fucking exist"),
objects: level_data.get("k48").expect("level object count doesnt exist").parse::<i32>().expect("object count not int"),
coins: level_data.get("k64").unwrap_or(&String::from("0")).parse::<i32>().expect("coins not int"),
has_ldm: level_data.get("k72").unwrap_or(&String::from("0")).parse::<i32>().expect("ldm not int"),
two_player: level_data.get("k43").unwrap_or(&String::from("0")).parse::<i32>().expect("two player not int")
};
let level_length = level.length.expect("level length doesnt fucking exist");
let level_object_count = level_data.get("k48").expect("level object count doesnt exist").parse::<i32>().expect("object count not int");
let level_coins = level_data.get("k64").unwrap_or(&String::from("0")).parse::<i32>().expect("coins not int");
let level_ldm = level_data.get("k72").unwrap_or(&String::from("0")).parse::<i32>().expect("ldm not int");
let level_two_player = level_data.get("k43").unwrap_or(&String::from("0")).parse::<i32>().expect("two player not int");
let inserted_level = diesel::insert_into(levels)
.values(&new_level)
.get_result::<Level, >(connection)
.expect("failed to insert level");
let inserted_level = sqlx::query!(
"INSERT INTO levels (name, user_id, description, game_version, binary_version, password, requested_stars, unlisted, version, extra_data, level_info, editor_time, editor_time_copies, song_id, length, objects, coins, has_ldm, two_player)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
level_name,
reupload_account_id,
level_description,
level_game_version,
level_binary_version,
level_password,
level_requested_stars,
0,
level_version,
extra_string,
default_level_info,
level_editor_time,
level_editor_time_copies,
level_song_id,
level_length,
level_object_count,
level_coins,
level_ldm,
level_two_player
)
.execute(connection)
.await
.expect("couldnt write to db");
fs::write(format!("{}/levels/{}.lvl", config::config_get_with_default("db.data_folder", "data"), inserted_level.id), general_purpose::URL_SAFE.decode(level_data.get("k4").expect("no level data?!").as_bytes()).expect("user provided invalid level string")).expect("couldnt write level to file");
// sqlite doesnt have return clause :frown: maybe swap to custom id system
let inserted_id = inserted_level.last_insert_rowid();
fs::write(format!("{}/levels/{}.lvl", config::config_get_with_default("db.data_folder", "data".to_string()), inserted_id), general_purpose::URL_SAFE.decode(level_data.get("k4").expect("no level data?!").as_bytes()).expect("user provided invalid level string")).expect("couldnt write level to file");
return Template::render("reupload", context! {
level_id: inserted_level.id
level_id: inserted_id
})
}