Compare commits
No commits in common. "c3bb6d0d6760602fbac79a770a7dac124eb11db0" and "1e0dea713509a680d9a2ab6f27540cf34ca7a544" have entirely different histories.
c3bb6d0d67
...
1e0dea7135
40 changed files with 1257 additions and 1609 deletions
|
@ -1 +1 @@
|
||||||
DATABASE_URL=sqlite://./database.db
|
DATABASE_URL=postgres://username:password@localhost/diesel_demo
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,7 +1,6 @@
|
||||||
/target
|
/target
|
||||||
|
|
||||||
/data
|
/data
|
||||||
*.db
|
|
||||||
|
|
||||||
.env
|
.env
|
||||||
config.toml
|
config.toml
|
899
Cargo.lock
generated
899
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -5,10 +5,10 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.21.3"
|
base64 = "0.21.3"
|
||||||
|
diesel = { version = "=2.1.0", features = ["postgres", "64-column-tables"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
flate2 = "1.0.27"
|
flate2 = "1.0.27"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
migrate = "0.2.0"
|
|
||||||
password-auth = "0.3.0"
|
password-auth = "0.3.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.9.4"
|
regex = "1.9.4"
|
||||||
|
@ -19,6 +19,4 @@ roxmltree = "0.18.0"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
sha = "1.0.3"
|
sha = "1.0.3"
|
||||||
sqlx = { version = "0.7.2", features = ["runtime-tokio", "sqlite"] }
|
|
||||||
tempfile = "3.8.0"
|
|
||||||
toml = "0.7.6"
|
toml = "0.7.6"
|
||||||
|
|
5
build.rs
5
build.rs
|
@ -1,5 +0,0 @@
|
||||||
// generated by `sqlx migrate build-script`
|
|
||||||
fn main() {
|
|
||||||
// trigger recompilation when a new migration is added
|
|
||||||
println!("cargo:rerun-if-changed=migrations");
|
|
||||||
}
|
|
|
@ -9,17 +9,12 @@
|
||||||
# boomlings.com/database/
|
# boomlings.com/database/
|
||||||
# example.com/aaaaaaaaaa/
|
# example.com/aaaaaaaaaa/
|
||||||
# ^^^^^^^^^^^
|
# ^^^^^^^^^^^
|
||||||
# leaving as empty will disable this
|
# leaving as "/" will disable this
|
||||||
append_path = ""
|
append_path = "/"
|
||||||
# where can your server be accessible (port)?
|
# where can your server be accessible?
|
||||||
port = 8000
|
port = 8000
|
||||||
# where can your server be accessible (domain)?
|
# your realip header, if you're behind a reverse proxy
|
||||||
# this is used for patching the executable
|
realip_header = "X-Real-IP"
|
||||||
#
|
|
||||||
# example:
|
|
||||||
# boomlings.com
|
|
||||||
# localhost:8000
|
|
||||||
domain = "localhost:8000"
|
|
||||||
|
|
||||||
[accounts]
|
[accounts]
|
||||||
# allow new accounts to be created
|
# allow new accounts to be created
|
||||||
|
|
9
diesel.toml
Normal file
9
diesel.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# 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"
|
0
migrations/.keep
Normal file
0
migrations/.keep
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-- 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();
|
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
-- 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;
|
3
migrations/2023-08-26-071607_accounts/down.sql
Normal file
3
migrations/2023-08-26-071607_accounts/down.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
DROP TABLE accounts;
|
||||||
|
|
||||||
|
DROP COLLATION case_insensitive;
|
|
@ -1,7 +1,13 @@
|
||||||
CREATE TABLE accounts (
|
CREATE COLLATION case_insensitive (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
provider = icu,
|
||||||
|
locale = 'und-u-ks-level2',
|
||||||
|
deterministic = false
|
||||||
|
);
|
||||||
|
|
||||||
username VARCHAR(20) NOT NULL COLLATE NOCASE UNIQUE,
|
CREATE TABLE accounts (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
|
||||||
|
username VARCHAR(20) NOT NULL COLLATE case_insensitive UNIQUE,
|
||||||
gjp2 TEXT NOT NULL, -- argon2 hashed (rubrub uses bcrypt but oh well)
|
gjp2 TEXT NOT NULL, -- argon2 hashed (rubrub uses bcrypt but oh well)
|
||||||
password 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,
|
email VARCHAR(254) NOT NULL,
|
||||||
|
@ -19,5 +25,5 @@ CREATE TABLE accounts (
|
||||||
twitter_url VARCHAR(20),
|
twitter_url VARCHAR(20),
|
||||||
twitch_url VARCHAR(20),
|
twitch_url VARCHAR(20),
|
||||||
|
|
||||||
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now'))
|
created_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))
|
||||||
);
|
);
|
|
@ -1,18 +1,14 @@
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
|
||||||
-- on a registered account, account_id refers to the
|
-- if `registered`, use account_id, else, use udid
|
||||||
-- 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,
|
udid TEXT,
|
||||||
account_id INTEGER references accounts(id),
|
account_id INTEGER references accounts(id),
|
||||||
registered INTEGER NOT NULL,
|
registered INTEGER NOT NULL,
|
||||||
|
|
||||||
username TEXT NOT NULL COLLATE NOCASE,
|
-- 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,
|
||||||
|
|
||||||
stars INTEGER NOT NULL DEFAULT 0,
|
stars INTEGER NOT NULL DEFAULT 0,
|
||||||
demons INTEGER NOT NULL DEFAULT 0,
|
demons INTEGER NOT NULL DEFAULT 0,
|
||||||
|
@ -38,8 +34,8 @@ CREATE TABLE users (
|
||||||
special INTEGER NOT NULL DEFAULT 0,
|
special INTEGER NOT NULL DEFAULT 0,
|
||||||
glow INTEGER NOT NULL DEFAULT 0,
|
glow INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
|
created_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
|
||||||
last_played TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
|
last_played TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
|
||||||
|
|
||||||
is_banned INTEGER NOT NULL DEFAULT 0,
|
is_banned INTEGER NOT NULL DEFAULT 0,
|
||||||
is_banned_upload INTEGER NOT NULL DEFAULT 0
|
is_banned_upload INTEGER NOT NULL DEFAULT 0
|
|
@ -1,11 +1,11 @@
|
||||||
CREATE TABLE levels (
|
CREATE TABLE levels (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
|
created_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
|
||||||
modified_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
|
modified_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
|
||||||
|
|
||||||
name VARCHAR(20) NOT NULL,
|
name VARCHAR(20) NOT NULL,
|
||||||
user_id INTEGER NOT NULL references users(id),
|
user_id INTEGER NOT NULL references users(id),
|
||||||
description VARCHAR(140) NOT NULL DEFAULT "",
|
description VARCHAR(140) NOT NULL DEFAULT '',
|
||||||
original INTEGER,
|
original INTEGER,
|
||||||
|
|
||||||
game_version INTEGER NOT NULL,
|
game_version INTEGER NOT NULL,
|
||||||
|
@ -16,8 +16,8 @@ CREATE TABLE levels (
|
||||||
unlisted INTEGER NOT NULL DEFAULT 0,
|
unlisted INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
version INTEGER NOT NULL DEFAULT 0,
|
version INTEGER NOT NULL DEFAULT 0,
|
||||||
extra_data BLOB NOT NULL,
|
extra_data BYTEA NOT NULL,
|
||||||
level_info BLOB NOT NULL,
|
level_info BYTEA NOT NULL,
|
||||||
|
|
||||||
editor_time INTEGER NOT NULL,
|
editor_time INTEGER NOT NULL,
|
||||||
editor_time_copies INTEGER NOT NULL,
|
editor_time_copies INTEGER NOT NULL,
|
||||||
|
@ -39,4 +39,4 @@ CREATE TABLE levels (
|
||||||
featured INTEGER NOT NULL DEFAULT 0,
|
featured INTEGER NOT NULL DEFAULT 0,
|
||||||
epic INTEGER NOT NULL DEFAULT 0,
|
epic INTEGER NOT NULL DEFAULT 0,
|
||||||
rated_coins INTEGER NOT NULL DEFAULT 0
|
rated_coins INTEGER NOT NULL DEFAULT 0
|
||||||
);
|
);
|
|
@ -1 +0,0 @@
|
||||||
DROP TABLE accounts;
|
|
43
readme.md
43
readme.md
|
@ -6,7 +6,7 @@ this project is based off of (stolen from) the [crystal-gauntlet](https://git.oa
|
||||||
|
|
||||||
## why?
|
## why?
|
||||||
|
|
||||||
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.
|
i've run out of ideas.
|
||||||
|
|
||||||
### features
|
### features
|
||||||
|
|
||||||
|
@ -17,40 +17,29 @@ i'm trying to learn some rust, and this is a solid choice. most GDPS solutions o
|
||||||
|
|
||||||
## build
|
## build
|
||||||
|
|
||||||
`cargo build --release`
|
### migrating databases
|
||||||
|
|
||||||
## configuration
|
- run `cargo install diesel_cli --no-default-features --features postgres`
|
||||||
|
- run `diesel migration run`
|
||||||
|
|
||||||
copy `.env.example` to `.env` and fill it out, same for `config.example.toml` to `config.toml`
|
### testing
|
||||||
|
|
||||||
## setup
|
- run `cargo run`
|
||||||
|
|
||||||
make sure you have `sqlx-cli` by running `cargo install sqlx-cli`
|
### building
|
||||||
|
|
||||||
run `sqlx database setup`
|
- run `cargo build --release`
|
||||||
|
|
||||||
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
|
## todo
|
||||||
|
|
||||||
- add get levels search back (find how to easily do dynamic queries)
|
|
||||||
- user icons in account management pages
|
|
||||||
- account settings page
|
- account settings page
|
||||||
- better web design (make formatting more consistant)
|
- better web design
|
||||||
- use chrono for dates in database
|
- user icons in the account management + settings (gdicon.oat.zone? selfhost?) ideally we find a legal way to do this (i cant distribute the plist+asset files directly) but doing this illegally is always an option
|
||||||
- 2.2's friends only unlisted
|
- use chrono
|
||||||
- dailies, weeklies, events(?)
|
- 2.2 friends only unlisted
|
||||||
|
- add dailies, events, weekly
|
||||||
- moderation utilities
|
- moderation utilities
|
||||||
- better song support
|
- better song support
|
||||||
- cache authentication (redis or mem)
|
- authentication caching
|
||||||
- make a proper rank system (reuploading, uploading music, rating, etc.)
|
- use log instead of println
|
||||||
- use serde to make the forms whateverCaseThisIs rather than breaking our lint convention
|
- make a proper rank system (reuploading, uploading music, rating, etc.)
|
||||||
- add back `realip` header support
|
|
||||||
- add configurable form limits
|
|
||||||
- nix
|
|
||||||
- clean up difficulty/demon difficulties. It's fucking VILE.
|
|
||||||
- panic less, use results
|
|
|
@ -1,34 +1,52 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use toml::Table;
|
#[derive(Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
use serde::de::DeserializeOwned;
|
pub general: ConfigGeneral,
|
||||||
|
pub accounts: ConfigAccounts,
|
||||||
pub static CONFIG: LazyLock<Table> = LazyLock::new(|| {
|
pub db: ConfigDB,
|
||||||
let toml_str = fs::read_to_string("config.toml").expect("error finding toml config");
|
pub levels: ConfigLevels
|
||||||
let config: Table = toml::from_str(toml_str.as_str()).expect("error parsing toml config");
|
|
||||||
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn config_get(key: &str) -> Option<&toml::Value> {
|
|
||||||
let this = &CONFIG;
|
|
||||||
let mut current_key = this.get(key.split(".").next()?)?;
|
|
||||||
for val in key.split(".").skip(1) {
|
|
||||||
current_key = current_key.get(val)?;
|
|
||||||
}
|
|
||||||
Some(current_key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_get_with_default<'de, T>(key: &str, default: T) -> T
|
#[derive(Deserialize)]
|
||||||
where
|
pub struct ConfigGeneral {
|
||||||
T: DeserializeOwned + 'de
|
pub append_path: String,
|
||||||
{
|
pub port: u16,
|
||||||
let val = config_get(key)
|
pub realip_header: String
|
||||||
.and_then(|v| v.to_owned().try_into().expect("invalid toml val"))
|
}
|
||||||
.unwrap_or_else(|| default);
|
|
||||||
|
|
||||||
return val;
|
#[derive(Deserialize)]
|
||||||
}
|
pub struct ConfigAccounts {
|
||||||
|
pub allow_registration: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ConfigDB {
|
||||||
|
pub data_folder: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ConfigLevels {
|
||||||
|
pub max_objects: i32,
|
||||||
|
pub blocklist: Vec<i32>,
|
||||||
|
pub reupload: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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:");
|
||||||
|
let config: Config = toml::from_str(toml_str.as_str()).expect("Error parsing toml config:");
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static CONFIG: LazyLock<Config> = LazyLock::new(|| {
|
||||||
|
let config = Config::load_from_file("config.toml");
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
11
src/db.rs
11
src/db.rs
|
@ -1,11 +1,14 @@
|
||||||
use sqlx::{Connection, SqliteConnection};
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub async fn establish_sqlite_conn() -> SqliteConnection {
|
pub mod models;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
|
pub fn establish_connection_pg() -> PgConnection {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
return SqliteConnection::connect(&database_url).await.expect("failed to connect to database");
|
PgConnection::establish(&database_url)
|
||||||
|
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||||
}
|
}
|
149
src/db/models.rs
Normal file
149
src/db/models.rs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
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
|
||||||
|
}
|
107
src/db/schema.rs
Normal file
107
src/db/schema.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
// @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,
|
||||||
|
);
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod login_account;
|
pub mod login_account;
|
||||||
pub mod register_account;
|
pub mod register_account;
|
||||||
|
pub mod update_account_settings;
|
|
@ -2,6 +2,8 @@ use rocket::form::Form;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::status;
|
use rocket::response::status;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::db;
|
use crate::db;
|
||||||
|
|
||||||
|
@ -15,43 +17,46 @@ pub struct FromLoginAccount {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/accounts/loginGJAccount.php", data = "<input>")]
|
#[post("/accounts/loginGJAccount.php", data = "<input>")]
|
||||||
pub async fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> {
|
pub fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> {
|
||||||
let connection = &mut db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
let username = helpers::clean::clean_basic(input.userName.as_ref());
|
if input.userName != helpers::clean::clean_no_space(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")
|
return status::Custom(Status::Ok, "-4")
|
||||||
}
|
}
|
||||||
|
|
||||||
// why does this check exist? it's kinda useless
|
// 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
|
||||||
if let Some(password) = password {
|
match input.password.clone() {
|
||||||
if password.len() < 6 {
|
Some(password_val) => {
|
||||||
return status::Custom(Status::Ok, "-8")
|
if password_val.len() < 6 {
|
||||||
}
|
return status::Custom(Status::Ok, "-8")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if username.len() < 3 {
|
if input.userName.len() < 3 {
|
||||||
return status::Custom(Status::Ok, "-9")
|
return status::Custom(Status::Ok, "-9")
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = sqlx::query_scalar!("SELECT id FROM accounts WHERE username = ?", username)
|
// account verification
|
||||||
.fetch_one(connection)
|
{
|
||||||
.await;
|
use crate::schema::accounts::dsl::*;
|
||||||
|
|
||||||
match result {
|
let query_result = accounts
|
||||||
Ok(account_id_val) => {
|
.select(id)
|
||||||
let user_id_val = helpers::accounts::get_user_id_from_account_id(account_id_val).await;
|
.filter(username.eq(input.userName.clone()))
|
||||||
|
.get_result::<i32, >(connection);
|
||||||
|
|
||||||
match helpers::accounts::auth(account_id_val, input.password.clone(), input.gjp.clone(), input.gjp2.clone()).await {
|
match query_result {
|
||||||
Ok(_) => return status::Custom(Status::Ok, Box::leak(format!("{},{}", user_id_val, account_id_val).into_boxed_str())),
|
Ok(account_id_val) => {
|
||||||
Err(_) => return status::Custom(Status::Ok, "-11")
|
let user_id_val = helpers::accounts::get_user_id_from_account_id(account_id_val);
|
||||||
}
|
|
||||||
},
|
match helpers::accounts::auth(account_id_val, input.password.clone(), input.gjp.clone(), input.gjp2.clone()) {
|
||||||
Err(_) => return status::Custom(Status::Ok, "-1")
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,9 +2,12 @@ use rocket::form::Form;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::status;
|
use rocket::response::status;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::result::Error;
|
||||||
|
|
||||||
use password_auth::generate_hash;
|
use password_auth::generate_hash;
|
||||||
|
|
||||||
use crate::config;
|
use crate::CONFIG;
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::db;
|
use crate::db;
|
||||||
|
|
||||||
|
@ -16,59 +19,77 @@ pub struct FormRegisterAccount {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/accounts/registerGJAccount.php", data = "<input>")]
|
#[post("/accounts/registerGJAccount.php", data = "<input>")]
|
||||||
pub async fn register_account(input: Form<FormRegisterAccount>) -> status::Custom<&'static str> {
|
pub fn register_account(input: Form<FormRegisterAccount>) -> status::Custom<&'static str> {
|
||||||
let mut connection = db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
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 {
|
if CONFIG.accounts.allow_registration == false {
|
||||||
return status::Custom(Status::Ok, "-1")
|
return status::Custom(Status::Ok, "-1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.userName != username {
|
if input.userName != helpers::clean::clean_no_space(input.userName.as_ref()) {
|
||||||
return status::Custom(Status::Ok, "-4")
|
return status::Custom(Status::Ok, "-4")
|
||||||
}
|
}
|
||||||
|
|
||||||
if password.len() < 6 {
|
if input.password.len() < 6 {
|
||||||
return status::Custom(Status::Ok, "-8")
|
return status::Custom(Status::Ok, "-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
if username.len() < 3 {
|
if input.userName.len() < 3 {
|
||||||
return status::Custom(Status::Ok, "-9")
|
return status::Custom(Status::Ok, "-9")
|
||||||
}
|
}
|
||||||
|
|
||||||
if username.len() > 20 {
|
if input.userName.len() > 20 {
|
||||||
return status::Custom(Status::Ok, "-4")
|
return status::Custom(Status::Ok, "-4")
|
||||||
}
|
}
|
||||||
|
|
||||||
if email.len() > 254 {
|
if input.email.len() > 254 {
|
||||||
return status::Custom(Status::Ok, "-6")
|
return status::Custom(Status::Ok, "-6")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the username is already taken
|
// account management
|
||||||
sqlx::query_scalar!("SELECT COUNT(*) FROM accounts WHERE username = ?", username)
|
use crate::models::{Account, NewAccount};
|
||||||
.fetch_one(&mut connection)
|
|
||||||
.await
|
|
||||||
.map_err(|_| status::Custom(Status::Ok, "-1"))
|
|
||||||
.expect("error getting the account count");
|
|
||||||
|
|
||||||
let inserted_account = sqlx::query!("INSERT INTO accounts (username, password, email, gjp2) VALUES (?, ?, ?, ?)", username, hashed_password, email, gjp2)
|
let inserted_account: Account;
|
||||||
.execute(&mut connection)
|
|
||||||
.await
|
|
||||||
.expect("error saving the new account");
|
|
||||||
|
|
||||||
let inserted_account_id = inserted_account.last_insert_rowid();
|
{
|
||||||
|
use crate::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("Fatal 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)
|
||||||
|
.expect("Fatal error saving the new account");
|
||||||
|
}
|
||||||
|
|
||||||
// user management
|
// user management
|
||||||
sqlx::query!("INSERT INTO users (account_id, username, registered) VALUES (?, ?, 1)", inserted_account_id, username)
|
use crate::models::{User, NewUser};
|
||||||
.execute(&mut connection)
|
|
||||||
.await
|
|
||||||
.expect("error saving the new user");
|
|
||||||
|
|
||||||
return status::Custom(Status::Ok, "1");
|
{
|
||||||
|
use crate::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)
|
||||||
|
.expect("Fatal error saving the new user");
|
||||||
|
}
|
||||||
|
|
||||||
|
return status::Custom(Status::Ok, "1")
|
||||||
}
|
}
|
62
src/endpoints/accounts/update_account_settings.rs
Normal file
62
src/endpoints/accounts/update_account_settings.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
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 crate::models::Account;
|
||||||
|
|
||||||
|
{
|
||||||
|
use crate::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")
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ use rocket::form::Form;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::status;
|
use rocket::response::status;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
|
||||||
use flate2::read::{GzDecoder, ZlibDecoder};
|
use flate2::read::{GzDecoder, ZlibDecoder};
|
||||||
|
@ -11,7 +13,6 @@ use std::fs;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::config;
|
|
||||||
use crate::db;
|
use crate::db;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
|
@ -22,11 +23,17 @@ pub struct FormDownloadLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/downloadGJLevel22.php", data = "<input>")]
|
#[post("/downloadGJLevel22.php", data = "<input>")]
|
||||||
pub async fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static str> {
|
pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static str> {
|
||||||
let mut connection = db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
|
use crate::schema::{levels, users};
|
||||||
|
|
||||||
|
use crate::models::{Level, User};
|
||||||
|
|
||||||
let mut response: Vec<String> = Vec::new();
|
let mut response: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
let query = levels::table.into_boxed();
|
||||||
|
|
||||||
match input.levelID {
|
match input.levelID {
|
||||||
-1 => {
|
-1 => {
|
||||||
unimplemented!("no daily support")
|
unimplemented!("no daily support")
|
||||||
|
@ -42,162 +49,168 @@ pub async fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
// database query
|
||||||
.fetch_one(&mut connection)
|
{
|
||||||
.await
|
let result = query
|
||||||
.expect("error loading levels");
|
.filter(levels::id.eq(input.levelID))
|
||||||
|
.get_result::<Level, >(connection)
|
||||||
|
.expect("fatal error loading levels");
|
||||||
|
|
||||||
let set_difficulty = match result.difficulty {
|
let user: User = users::table.find(result.user_id).get_result::<User, >(connection).expect("couldnt get user from lvl");
|
||||||
Some(diff) => {
|
let level: Level = result;
|
||||||
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
|
||||||
},
|
let set_difficulty = match level.difficulty {
|
||||||
None => None
|
Some(diff) => {
|
||||||
};
|
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||||
let community_difficulty = match result.community_difficulty {
|
},
|
||||||
Some(diff) => {
|
None => None
|
||||||
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
};
|
||||||
},
|
let community_difficulty = match level.community_difficulty {
|
||||||
None => None
|
Some(diff) => {
|
||||||
};
|
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||||
let difficulty = match set_difficulty {
|
},
|
||||||
Some(diff) => {
|
None => None
|
||||||
Some(diff)
|
};
|
||||||
},
|
let difficulty = match set_difficulty {
|
||||||
None => {
|
Some(diff) => {
|
||||||
match community_difficulty {
|
Some(diff)
|
||||||
Some(diff) => {
|
},
|
||||||
Some(diff)
|
None => {
|
||||||
},
|
match community_difficulty {
|
||||||
None => None
|
Some(diff) => {
|
||||||
|
Some(diff)
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
let demon_difficulty = match level.demon_difficulty {
|
||||||
let demon_difficulty = match result.demon_difficulty {
|
Some(diff) => {
|
||||||
Some(diff) => {
|
Some(helpers::difficulty::DemonDifficulty::new(diff))
|
||||||
Some(helpers::difficulty::DemonDifficulty::new(diff))
|
},
|
||||||
},
|
None => None
|
||||||
None => None
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let xor_pass: String;
|
let xor_pass: String;
|
||||||
if input.gameVersion.unwrap_or(19) >= 20 {
|
if input.gameVersion.unwrap_or(19) >= 20 {
|
||||||
xor_pass = general_purpose::URL_SAFE.encode(helpers::encryption::cyclic_xor_string(&result.password.clone().unwrap_or(String::from("0")), "26364"))
|
xor_pass = general_purpose::URL_SAFE.encode(helpers::encryption::cyclic_xor_string(&level.password.clone().unwrap_or(String::from("0")), "26364"))
|
||||||
} else {
|
|
||||||
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".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!!
|
|
||||||
let mut gz_decoder = GzDecoder::new(compressed_level_data.as_slice());
|
|
||||||
let mut decompressed_data = Vec::new();
|
|
||||||
gz_decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level");
|
|
||||||
decompressed_data
|
|
||||||
} else if compressed_level_data.starts_with(&[0x78]) {
|
|
||||||
// zlib!!
|
|
||||||
let mut zlib_decoder = ZlibDecoder::new(compressed_level_data.as_slice());
|
|
||||||
let mut decompressed_data = Vec::new();
|
|
||||||
zlib_decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level");
|
|
||||||
decompressed_data
|
|
||||||
} else {
|
|
||||||
panic!("invalid compression method")
|
|
||||||
}).expect("invalid utf-8 sequence");
|
|
||||||
|
|
||||||
let level_data = uncompressed_level_data.as_bytes();
|
|
||||||
|
|
||||||
response.push(helpers::format::format(hashmap! {
|
|
||||||
1 => result.id.to_string(),
|
|
||||||
2 => result.name,
|
|
||||||
3 => if input.gameVersion.unwrap_or(19) >= 20 {
|
|
||||||
general_purpose::URL_SAFE.encode(result.description)
|
|
||||||
} else {
|
} else {
|
||||||
result.description
|
xor_pass = level.password.clone().unwrap_or(String::from("0"));
|
||||||
},
|
}
|
||||||
4 => String::from_utf8(level_data.to_vec()).expect("invalid utf-8 sequence"),
|
|
||||||
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(),
|
|
||||||
9 => (match difficulty {
|
|
||||||
Some(diff) => diff.to_star_difficulty(),
|
|
||||||
None => 0
|
|
||||||
} * 10).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 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => 0
|
|
||||||
}.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 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => 0
|
|
||||||
}.to_string(),
|
|
||||||
27 => xor_pass,
|
|
||||||
28 => "1".to_string(), // unimplemented
|
|
||||||
29 => "1".to_string(), // unimplemented
|
|
||||||
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 => result.epic.to_string(),
|
|
||||||
43 => match demon_difficulty {
|
|
||||||
Some(diff) => {
|
|
||||||
diff
|
|
||||||
},
|
|
||||||
None => helpers::difficulty::DemonDifficulty::Hard
|
|
||||||
}.to_demon_difficulty().to_string(),
|
|
||||||
44 => "0".to_string(), // unimplemented
|
|
||||||
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 = [
|
let compressed_level_data = fs::read(format!("{}/{}/{}.lvl", crate::CONFIG.db.data_folder, "levels", level.id)).expect("couldnt read level file");
|
||||||
result.user_id.to_string(),
|
|
||||||
result.stars.unwrap_or(0).to_string(),
|
let uncompressed_level_data = String::from_utf8(if compressed_level_data.starts_with(&[0x1F, 0x8B]) {
|
||||||
match difficulty {
|
// gzip!!
|
||||||
Some(diff) => {
|
let mut gz_decoder = GzDecoder::new(compressed_level_data.as_slice());
|
||||||
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
let mut decompressed_data = Vec::new();
|
||||||
1
|
gz_decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level");
|
||||||
} else {
|
decompressed_data
|
||||||
0
|
} else if compressed_level_data.starts_with(&[0x78]) {
|
||||||
}
|
// zlib!!
|
||||||
|
let mut zlib_decoder = ZlibDecoder::new(compressed_level_data.as_slice());
|
||||||
|
let mut decompressed_data = Vec::new();
|
||||||
|
zlib_decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level");
|
||||||
|
decompressed_data
|
||||||
|
} else {
|
||||||
|
panic!("invalid compression method")
|
||||||
|
}).expect("invalid utf-8 sequence");
|
||||||
|
|
||||||
|
let level_data = uncompressed_level_data.as_bytes();
|
||||||
|
|
||||||
|
response.push(helpers::format::format(hashmap! {
|
||||||
|
1 => level.id.to_string(),
|
||||||
|
2 => level.name,
|
||||||
|
3 => if input.gameVersion.unwrap_or(19) >= 20 {
|
||||||
|
general_purpose::URL_SAFE.encode(level.description)
|
||||||
|
} else {
|
||||||
|
level.description
|
||||||
},
|
},
|
||||||
None => 0
|
4 => String::from_utf8(level_data.to_vec()).expect("invalid utf-8 sequence"),
|
||||||
}.to_string(),
|
5 => level.version.to_string(),
|
||||||
result.id.to_string(),
|
6 => user.id.to_string(),
|
||||||
result.rated_coins.to_string(),
|
// this argument is weird. its the "difficulty divisor"
|
||||||
result.featured.to_string(),
|
// used to be vote count but yeah
|
||||||
result.password.unwrap_or(String::new()).to_string(),
|
8 => 10.to_string(),
|
||||||
0.to_string()
|
9 => (match difficulty {
|
||||||
];
|
Some(diff) => diff.to_star_difficulty(),
|
||||||
response.push(helpers::encryption::gen_solo_2(thing.join(",")));
|
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(),
|
||||||
|
17 => match difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => 0
|
||||||
|
}.to_string(),
|
||||||
|
18 => (if let Some(stars) = level.stars { stars } else { 0 }).to_string(),
|
||||||
|
19 => level.featured.to_string(),
|
||||||
|
25 => match difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
if diff == helpers::difficulty::LevelDifficulty::Auto {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => 0
|
||||||
|
}.to_string(),
|
||||||
|
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(),
|
||||||
|
41 => "".to_string(), // unimplemented
|
||||||
|
42 => level.epic.to_string(),
|
||||||
|
43 => match demon_difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
diff
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
}));
|
||||||
|
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(),
|
||||||
|
match difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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(),
|
||||||
|
0.to_string()
|
||||||
|
];
|
||||||
|
response.push(helpers::encryption::gen_solo_2(thing.join(",")));
|
||||||
|
}
|
||||||
|
|
||||||
return status::Custom(Status::Ok, Box::leak(response.join("#").into_boxed_str()))
|
return status::Custom(Status::Ok, Box::leak(response.join("#").into_boxed_str()))
|
||||||
}
|
}
|
|
@ -2,53 +2,19 @@ use rocket::form::Form;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::status;
|
use rocket::response::status;
|
||||||
|
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use sqlx::Type;
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
use sqlx::{Encode, Sqlite, query_builder::QueryBuilder, Execute};
|
|
||||||
|
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::db;
|
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)]
|
#[derive(FromForm)]
|
||||||
pub struct FormGetLevels {
|
pub struct FormGetLevels {
|
||||||
page: Option<i64>,
|
page: i64,
|
||||||
str: Option<String>,
|
str: String,
|
||||||
|
|
||||||
accountID: Option<i64>,
|
accountID: Option<i32>,
|
||||||
gjp: Option<String>,
|
gjp: Option<String>,
|
||||||
gjp2: Option<String>,
|
gjp2: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
|
@ -75,57 +41,55 @@ pub struct FormGetLevels {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/getGJLevels20.php", data = "<input>")]
|
#[post("/getGJLevels20.php", data = "<input>")]
|
||||||
pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
|
pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
|
||||||
let mut connection = db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
|
use crate::schema::{levels, users};
|
||||||
|
use crate::models::{Level, User};
|
||||||
|
|
||||||
let mut can_see_unlisted = false;
|
let mut can_see_unlisted = false;
|
||||||
|
|
||||||
|
let mut query = levels::table.into_boxed();
|
||||||
|
let mut count_query = levels::table.into_boxed();
|
||||||
|
|
||||||
// WHERE [...]
|
if input.str != "" && input.r#type != Some(5) && input.r#type != Some(10) && input.r#type != Some(19) {
|
||||||
let mut query_params: Vec<&str> = vec![];
|
match input.str.parse::<i32>() {
|
||||||
// Use this for binding on `query_params`
|
Ok(matched_id) => {
|
||||||
let mut query_params_bind: Vec<Box<dyn ToString + Send>> = vec![];
|
|
||||||
// ORDER BY [...]
|
|
||||||
let mut order = "levels.created_at DESC";
|
|
||||||
|
|
||||||
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;
|
can_see_unlisted = true;
|
||||||
query_params.push("levels.id = ?");
|
query = query.filter(levels::id.eq(matched_id));
|
||||||
query_params_bind.push(Box::new(id))
|
count_query = count_query.filter(levels::id.eq(matched_id))
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
query_params.push("levels.name LIKE ?");
|
query = query.filter(levels::name.ilike(input.str.to_owned() + "%"));
|
||||||
query_params_bind.push(Box::new(search_query.clone() + "%"));
|
count_query = count_query.filter(levels::name.ilike(input.str.to_owned() + "%"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(1) = input.featured {
|
if let Some(1) = input.featured {
|
||||||
query_params.push("featured = 1");
|
query = query.filter(levels::featured.eq(1));
|
||||||
|
count_query = count_query.filter(levels::featured.eq(1))
|
||||||
}
|
}
|
||||||
if let Some(1) = input.original {
|
if let Some(1) = input.original {
|
||||||
query_params.push("original IS NULL");
|
query = query.filter(levels::original.is_null());
|
||||||
|
count_query = count_query.filter(levels::original.is_null())
|
||||||
}
|
}
|
||||||
if let Some(1) = input.coins {
|
if let Some(1) = input.coins {
|
||||||
query_params.push("rated_coins = 1 AND levels.coins != 0");
|
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)))
|
||||||
}
|
}
|
||||||
if let Some(1) = input.epic {
|
if let Some(1) = input.epic {
|
||||||
query_params.push("epic = 1");
|
query = query.filter(levels::epic.eq(1));
|
||||||
|
count_query = count_query.filter(levels::epic.eq(1))
|
||||||
}
|
}
|
||||||
if let Some(1) = input.uncompleted {
|
if let Some(1) = input.uncompleted {
|
||||||
match input.completedLevels.clone() {
|
match input.completedLevels.clone() {
|
||||||
Some(completed_levels) => {
|
Some(completed_levels) => {
|
||||||
let clean_levels: Vec<i64> = completed_levels[1..completed_levels.len() - 1].split(',')
|
let clean_levels: Vec<i32> = completed_levels[1..completed_levels.len() - 1].split(',')
|
||||||
.map(|s| s.parse::<i64>().expect("failed to parse i64"))
|
.map(|s| s.parse::<i32>().expect("failed to parse i32"))
|
||||||
.collect();
|
.collect();
|
||||||
let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
|
query = query.filter(levels::id.ne_all(clean_levels.clone()));
|
||||||
query_params.push("levels.id NOT IN (?)");
|
count_query = count_query.filter(levels::id.ne_all(clean_levels))
|
||||||
query_params_bind.push(Box::new(levels_str));
|
|
||||||
},
|
},
|
||||||
None => return status::Custom(Status::Ok, "-1")
|
None => return status::Custom(Status::Ok, "-1")
|
||||||
}
|
}
|
||||||
|
@ -133,92 +97,91 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
if let Some(1) = input.onlyCompleted {
|
if let Some(1) = input.onlyCompleted {
|
||||||
match input.completedLevels.clone() {
|
match input.completedLevels.clone() {
|
||||||
Some(completed_levels) => {
|
Some(completed_levels) => {
|
||||||
let clean_levels: Vec<i64> = completed_levels[1..completed_levels.len() - 1].split(',')
|
let clean_levels: Vec<i32> = completed_levels[1..completed_levels.len() - 1].split(',')
|
||||||
.map(|s| s.parse::<i64>().expect("failed to parse i64"))
|
.map(|s| s.parse::<i32>().expect("failed to parse i32"))
|
||||||
.collect();
|
.collect();
|
||||||
let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
|
query = query.filter(levels::id.eq_any(clean_levels.clone()));
|
||||||
query_params.push("levels.id IN (?)");
|
count_query = count_query.filter(levels::id.eq_any(clean_levels))
|
||||||
query_params_bind.push(Box::new(levels_str));
|
|
||||||
},
|
},
|
||||||
None => return status::Custom(Status::Ok, "-1")
|
None => return status::Custom(Status::Ok, "-1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(song_id) = input.song {
|
if let Some(song_id) = input.song {
|
||||||
if let Some(custom_song) = input.customSong {
|
if let Some(custom_song) = input.customSong {
|
||||||
query_params.push("song_id = ?");
|
query = query.filter(levels::song_id.eq(custom_song));
|
||||||
query_params_bind.push(Box::new(custom_song));
|
count_query = count_query.filter(levels::song_id.eq(custom_song))
|
||||||
} else {
|
} else {
|
||||||
query_params.push("song_id = ?");
|
query = query.filter(levels::song_id.eq(song_id));
|
||||||
query_params_bind.push(Box::new(song_id));
|
count_query = count_query.filter(levels::song_id.eq(song_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(1) = input.twoPlayer {
|
if let Some(1) = input.twoPlayer {
|
||||||
query_params.push("two_player = 1");
|
query = query.filter(levels::two_player.eq(1));
|
||||||
|
count_query = count_query.filter(levels::two_player.eq(1))
|
||||||
}
|
}
|
||||||
if let Some(1) = input.star {
|
if let Some(1) = input.star {
|
||||||
query_params.push("levels.stars IS NOT NULL");
|
query = query.filter(levels::stars.is_not_null());
|
||||||
|
count_query = count_query.filter(levels::stars.is_not_null())
|
||||||
}
|
}
|
||||||
if let Some(1) = input.noStar {
|
if let Some(1) = input.noStar {
|
||||||
query_params.push("levels.stars IS NULL");
|
query = query.filter(levels::stars.is_null());
|
||||||
|
count_query = count_query.filter(levels::stars.is_null())
|
||||||
}
|
}
|
||||||
if let Some(_gauntlet_id) = input.gauntlet {
|
if let Some(_gauntlet_id) = input.gauntlet {
|
||||||
unimplemented!("no gauntlet support")
|
unimplemented!("no gauntlet support")
|
||||||
}
|
}
|
||||||
if let Some(len) = input.len {
|
if let Some(len) = input.len {
|
||||||
query_params.push("levels.length = ?");
|
query = query.filter(levels::length.eq(len));
|
||||||
query_params_bind.push(Box::new(len));
|
count_query = count_query.filter(levels::length.eq(len))
|
||||||
}
|
}
|
||||||
if let Some(diff) = input.diff.clone() {
|
if let Some(diff) = input.diff.clone() {
|
||||||
if diff != "-" {
|
if diff != "-" {
|
||||||
match diff.as_str() {
|
match diff.as_str() {
|
||||||
"-1" => {
|
"-1" => {
|
||||||
query_params.push("difficulty IS NULL AND community_difficulty IS NULL"); // NA
|
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()))
|
||||||
},
|
},
|
||||||
"-2" => match input.demonFilter {
|
"-2" => match input.demonFilter {
|
||||||
Some(demon_filter) => {
|
Some(demon_filter) => {
|
||||||
match demon_filter {
|
match demon_filter {
|
||||||
1 => {
|
1 => {
|
||||||
query_params.push("demon_difficulty = ?");
|
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Easy.to_demon_difficulty()));
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Easy.to_demon_difficulty()));
|
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Easy.to_demon_difficulty()))
|
||||||
},
|
},
|
||||||
2 => {
|
2 => {
|
||||||
query_params.push("demon_difficulty = ?");
|
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Medium.to_demon_difficulty()));
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Medium.to_demon_difficulty()));
|
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Medium.to_demon_difficulty()))
|
||||||
},
|
},
|
||||||
3 => {
|
3 => {
|
||||||
query_params.push("demon_difficulty = ?");
|
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Hard.to_demon_difficulty()));
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Hard.to_demon_difficulty()));
|
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Hard.to_demon_difficulty()))
|
||||||
},
|
},
|
||||||
4 => {
|
4 => {
|
||||||
query_params.push("demon_difficulty = ?");
|
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Insane.to_demon_difficulty()));
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Insane.to_demon_difficulty()));
|
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Insane.to_demon_difficulty()))
|
||||||
},
|
},
|
||||||
5 => {
|
5 => {
|
||||||
query_params.push("demon_difficulty = ?");
|
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Extreme.to_demon_difficulty()));
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Extreme.to_demon_difficulty()));
|
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Extreme.to_demon_difficulty()))
|
||||||
},
|
},
|
||||||
_ => panic!("invalid demon filter!")
|
_ => panic!("invalid demon filter!")
|
||||||
}
|
}
|
||||||
query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)");
|
query = query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(crate::difficulty::LevelDifficulty::Demon.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(crate::difficulty::LevelDifficulty::Demon.to_star_difficulty()))));
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()));
|
count_query = count_query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(crate::difficulty::LevelDifficulty::Demon.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(crate::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")
|
None => panic!("demon filter option with no demon filter argument")
|
||||||
},
|
},
|
||||||
"-3" => {
|
"-3" => {
|
||||||
query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)");
|
query = query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty()))));
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()));
|
count_query = count_query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::<i32>(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::<i32>(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty()))))
|
||||||
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()));
|
|
||||||
},
|
},
|
||||||
// easy, normal, hard, harder, insane
|
// easy, normal, hard, harder, insane
|
||||||
_ => {
|
_ => {
|
||||||
let clean_diffs: Vec<i32> = diff.split(',')
|
let diffs: Vec<i32> = diff.split(',')
|
||||||
.map(|v| v.parse::<i32>().expect("couldnt parse i32"))
|
.map(|v| v.parse::<i32>().expect("couldnt parse i32"))
|
||||||
.collect();
|
.collect();
|
||||||
let diffs_str = clean_diffs.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
|
query = query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs.clone()))));
|
||||||
query_params.push("difficulty IN (?) OR (difficulty IS NULL AND community_difficulty IN (?))");
|
count_query = count_query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs))))
|
||||||
query_params_bind.push(Box::new(diffs_str.clone()));
|
|
||||||
query_params_bind.push(Box::new(diffs_str));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,11 +191,13 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
match search_type {
|
match search_type {
|
||||||
// downloads
|
// downloads
|
||||||
1 => {
|
1 => {
|
||||||
order = "levels.downloads DESC";
|
query = query.order(levels::downloads.desc());
|
||||||
|
// count query order doesnt matter
|
||||||
},
|
},
|
||||||
// likes
|
// likes
|
||||||
2 => {
|
2 => {
|
||||||
order = "levels.likes DESC";
|
query = query.order(levels::likes.desc());
|
||||||
|
// count query order doesnt matter
|
||||||
},
|
},
|
||||||
// trending
|
// trending
|
||||||
3 => {
|
3 => {
|
||||||
|
@ -247,9 +212,9 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
5 => {
|
5 => {
|
||||||
if let Some(1) = input.local {
|
if let Some(1) = input.local {
|
||||||
if let Some(input_account_id) = input.accountID {
|
if let Some(input_account_id) = input.accountID {
|
||||||
let (user_id_val, _account_id_val): (i64, i64);
|
let (user_id_val, _account_id_val): (i32, i32);
|
||||||
|
|
||||||
match helpers::accounts::auth(input_account_id, input.password.clone(), input.gjp.clone(), input.gjp2.clone()).await {
|
match helpers::accounts::auth(input_account_id, input.password.clone(), input.gjp.clone(), input.gjp2.clone()) {
|
||||||
Ok((user_id_val_auth, account_id_val_auth)) => {
|
Ok((user_id_val_auth, account_id_val_auth)) => {
|
||||||
user_id_val = user_id_val_auth;
|
user_id_val = user_id_val_auth;
|
||||||
_account_id_val = account_id_val_auth;
|
_account_id_val = account_id_val_auth;
|
||||||
|
@ -257,34 +222,37 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
Err(_) => return status::Custom(Status::Ok, "-1")
|
Err(_) => return status::Custom(Status::Ok, "-1")
|
||||||
};
|
};
|
||||||
|
|
||||||
if user_id_val == search_query.parse::<i64>().expect("couldnt convert query input to i64") {
|
if user_id_val == input.str.parse::<i32>().expect("couldnt convert query input to i32") {
|
||||||
can_see_unlisted = true;
|
can_see_unlisted = true;
|
||||||
query_params.push("levels.user_id = ?");
|
query = query.filter(levels::user_id.eq(user_id_val));
|
||||||
query_params_bind.push(Box::new(user_id_val));
|
count_query = count_query.filter(levels::user_id.eq(user_id_val))
|
||||||
} else {
|
} else {
|
||||||
return status::Custom(Status::Ok, "-1")
|
return status::Custom(Status::Ok, "-1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let None = input.local {
|
if let None = input.local {
|
||||||
let user_id_val = search_query.parse::<i64>().expect("couldnt convert query input to i64");
|
let user_id_val = input.str.parse::<i32>().expect("couldnt convert query input to i32");
|
||||||
|
|
||||||
query_params.push("levels.user_id = ?");
|
query = query.filter(levels::user_id.eq(user_id_val));
|
||||||
query_params_bind.push(Box::new(user_id_val));
|
count_query = count_query.filter(levels::user_id.eq(user_id_val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// featured
|
// featured
|
||||||
// 17 is gdworld
|
// 17 is gdworld
|
||||||
6 | 17 => {
|
6 | 17 => {
|
||||||
query_params.push("featured = 1");
|
query = query.filter(levels::featured.eq(1));
|
||||||
|
count_query = count_query.filter(levels::featured.eq(1))
|
||||||
},
|
},
|
||||||
// epic / HoF
|
// epic / HoF
|
||||||
16 => {
|
16 => {
|
||||||
query_params.push("epic = 1");
|
query = query.filter(levels::epic.eq(1));
|
||||||
|
count_query = count_query.filter(levels::epic.eq(1))
|
||||||
},
|
},
|
||||||
// magic
|
// magic
|
||||||
7 => {
|
7 => {
|
||||||
query_params.push("objects > 4000");
|
query = query.filter(levels::objects.gt(4000));
|
||||||
|
count_query = count_query.filter(levels::objects.gt(4000))
|
||||||
},
|
},
|
||||||
// map packs 🙄😶
|
// map packs 🙄😶
|
||||||
10 | 19 => {
|
10 | 19 => {
|
||||||
|
@ -292,7 +260,8 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
},
|
},
|
||||||
// rated
|
// rated
|
||||||
11 => {
|
11 => {
|
||||||
query_params.push("levels.stars IS NOT NULL");
|
query = query.filter(levels::stars.is_not_null());
|
||||||
|
count_query = count_query.filter(levels::stars.is_not_null())
|
||||||
},
|
},
|
||||||
// followed
|
// followed
|
||||||
12 => {
|
12 => {
|
||||||
|
@ -317,63 +286,72 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
// default sort
|
// default sort
|
||||||
// 15 is gdworld
|
// 15 is gdworld
|
||||||
0 | 15 | _ => {
|
0 | 15 | _ => {
|
||||||
order = "likes DESC";
|
query = query.order(levels::likes.desc());
|
||||||
|
// count query order doesnt matter
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !can_see_unlisted {
|
if !can_see_unlisted {
|
||||||
query_params.push("unlisted = 0");
|
query = query.filter(levels::unlisted.eq(0));
|
||||||
|
count_query = count_query.filter(levels::unlisted.eq(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
let where_str = format!("WHERE ({})", query_params.join(" AND "));
|
let mut results: Vec<String> = [].to_vec();
|
||||||
let query_base = format!("FROM levels JOIN users ON levels.user_id = users.id {} ORDER BY {}", where_str, order);
|
let mut users: Vec<String> = [].to_vec();
|
||||||
|
let mut songs: Vec<String> = [].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 hash_data: Vec<(i32, i32, bool)> = [].to_vec();
|
||||||
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 {
|
for result in {
|
||||||
query
|
query
|
||||||
.fetch_all(&mut connection)
|
.order(levels::created_at.desc())
|
||||||
.await
|
.offset(input.page * 10)
|
||||||
.expect("error loading levels")
|
.limit(10)
|
||||||
|
.get_results::<Level, >(connection)
|
||||||
|
.expect("fatal error loading levels")
|
||||||
} {
|
} {
|
||||||
let set_difficulty = result.difficulty.map(helpers::difficulty::LevelDifficulty::new);
|
let user: User = users::table.find(result.user_id).get_result::<User, >(connection).expect("couldnt get user from lvl");
|
||||||
let community_difficulty = result.community_difficulty.map(helpers::difficulty::LevelDifficulty::new);
|
let level: Level = result;
|
||||||
let difficulty = set_difficulty.or(community_difficulty);
|
|
||||||
let demon_difficulty = result.demon_difficulty.map(helpers::difficulty::DemonDifficulty::new);
|
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
|
||||||
|
};
|
||||||
|
|
||||||
results.push(helpers::format::format(hashmap! {
|
results.push(helpers::format::format(hashmap! {
|
||||||
1 => result.id.to_string(),
|
1 => level.id.to_string(),
|
||||||
2 => result.name,
|
2 => level.name,
|
||||||
3 => general_purpose::URL_SAFE.encode(result.description),
|
3 => general_purpose::URL_SAFE.encode(level.description),
|
||||||
5 => result.version.to_string(),
|
5 => level.version.to_string(),
|
||||||
6 => result.user_id.to_string(),
|
6 => user.id.to_string(),
|
||||||
// this argument is weird. its the "difficulty divisor"
|
// this argument is weird. its the "difficulty divisor"
|
||||||
// used to be vote count but yeah
|
// used to be vote count but yeah
|
||||||
8 => 10.to_string(),
|
8 => 10.to_string(),
|
||||||
|
@ -381,12 +359,12 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
Some(diff) => diff.to_star_difficulty(),
|
Some(diff) => diff.to_star_difficulty(),
|
||||||
None => 0
|
None => 0
|
||||||
} * 10).to_string(),
|
} * 10).to_string(),
|
||||||
10 => result.downloads.to_string(),
|
10 => level.downloads.to_string(),
|
||||||
12 => (if result.song_id < 50 { result.song_id } else { 0 }).to_string(),
|
12 => (if level.song_id < 50 { level.song_id } else { 0 }).to_string(),
|
||||||
13 => result.game_version.to_string(),
|
13 => level.game_version.to_string(),
|
||||||
14 => result.likes.to_string(),
|
14 => level.likes.to_string(),
|
||||||
16 => (-result.likes).to_string(),
|
16 => (-level.likes).to_string(),
|
||||||
15 => result.length.to_string(),
|
15 => level.length.to_string(),
|
||||||
17 => match difficulty {
|
17 => match difficulty {
|
||||||
Some(diff) => {
|
Some(diff) => {
|
||||||
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
||||||
|
@ -397,8 +375,8 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
},
|
},
|
||||||
None => 0
|
None => 0
|
||||||
}.to_string(),
|
}.to_string(),
|
||||||
18 => (if let Some(stars) = result.stars { stars } else { 0 }).to_string(),
|
18 => (if let Some(stars) = level.stars { stars } else { 0 }).to_string(),
|
||||||
19 => result.featured.to_string(),
|
19 => level.featured.to_string(),
|
||||||
25 => match difficulty {
|
25 => match difficulty {
|
||||||
Some(diff) => {
|
Some(diff) => {
|
||||||
if diff == helpers::difficulty::LevelDifficulty::Auto {
|
if diff == helpers::difficulty::LevelDifficulty::Auto {
|
||||||
|
@ -409,49 +387,54 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
||||||
},
|
},
|
||||||
None => 0
|
None => 0
|
||||||
}.to_string(),
|
}.to_string(),
|
||||||
30 => (if let Some(original) = result.original { original } else { 0 }).to_string(),
|
30 => (if let Some(original) = level.original { original } else { 0 }).to_string(),
|
||||||
31 => result.two_player.to_string(),
|
31 => level.two_player.to_string(),
|
||||||
35 => (if result.song_id >= 50 { result.song_id } else { 0 }).to_string(),
|
35 => (if level.song_id >= 50 { level.song_id } else { 0 }).to_string(),
|
||||||
37 => result.coins.to_string(),
|
37 => level.coins.to_string(),
|
||||||
38 => result.rated_coins.to_string(),
|
38 => level.rated_coins.to_string(),
|
||||||
39 => (if let Some(requested_stars) = result.requested_stars { requested_stars } else { 0 }).to_string(),
|
39 => (if let Some(requested_stars) = level.requested_stars { requested_stars } else { 0 }).to_string(),
|
||||||
40 => result.has_ldm.to_string(),
|
40 => level.has_ldm.to_string(),
|
||||||
42 => result.epic.to_string(),
|
42 => level.epic.to_string(),
|
||||||
43 => match demon_difficulty {
|
43 => match demon_difficulty {
|
||||||
Some(diff) => {
|
Some(diff) => {
|
||||||
diff
|
diff
|
||||||
},
|
},
|
||||||
None => helpers::difficulty::DemonDifficulty::Hard
|
None => helpers::difficulty::DemonDifficulty::Hard
|
||||||
}.to_demon_difficulty().to_string(),
|
}.to_demon_difficulty().to_string(),
|
||||||
45 => result.objects.to_string(),
|
45 => level.objects.to_string(),
|
||||||
46 => result.editor_time.to_string(),
|
46 => level.editor_time.to_string(),
|
||||||
47 => result.editor_time_copies.to_string()
|
47 => level.editor_time_copies.to_string()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
users.push(format!("{}:{}:{}", result.user_id, result.user_username, {
|
users.push(format!("{}:{}:{}", user.id, user.username, {
|
||||||
if result.user_registered == 1 {
|
if user.registered == 1 {
|
||||||
result.user_account_id.expect("wtf? registered user with no account id.").to_string()
|
user.account_id.expect("wtf? registered user with no account id.").to_string()
|
||||||
} else {
|
} else {
|
||||||
result.user_udid.expect("wtf? unregistered user with no udid.")
|
user.udid.expect("wtf? unregistered user with no udid.")
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
hash_data.push((
|
hash_data.push((
|
||||||
result.id,
|
level.id,
|
||||||
{ if let Some(stars) = result.stars {
|
{ if let Some(stars) = level.stars {
|
||||||
stars
|
stars
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}},
|
}},
|
||||||
{ if let 1 = result.rated_coins {
|
{ if let 1 = level.rated_coins {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}}
|
}}
|
||||||
));
|
));
|
||||||
}
|
};
|
||||||
|
|
||||||
let search_meta = format!("{}:{}:{}", count, page_offset, 10);
|
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 response = vec![results.join("|"), users.join("|"), songs.join("|"), search_meta, helpers::encryption::gen_multi(hash_data)].join("#");
|
let response = vec![results.join("|"), users.join("|"), songs.join("|"), search_meta, helpers::encryption::gen_multi(hash_data)].join("#");
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,19 @@ use rocket::form::Form;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::status;
|
use rocket::response::status;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::config;
|
use crate::config::CONFIG;
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::db;
|
use crate::db;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct FormUploadLevel {
|
pub struct FormUploadLevel {
|
||||||
accountID: i64,
|
accountID: i32,
|
||||||
|
|
||||||
gjp: Option<String>,
|
gjp: Option<String>,
|
||||||
gjp2: Option<String>,
|
gjp2: Option<String>,
|
||||||
|
@ -22,7 +24,7 @@ pub struct FormUploadLevel {
|
||||||
audioTrack: i32,
|
audioTrack: i32,
|
||||||
levelName: String,
|
levelName: String,
|
||||||
levelDesc: String,
|
levelDesc: String,
|
||||||
levelID: i64,
|
levelID: i32,
|
||||||
levelVersion: i32,
|
levelVersion: i32,
|
||||||
levelInfo: String,
|
levelInfo: String,
|
||||||
levelString: String,
|
levelString: String,
|
||||||
|
@ -38,14 +40,14 @@ pub struct FormUploadLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/uploadGJLevel21.php", data = "<input>")]
|
#[post("/uploadGJLevel21.php", data = "<input>")]
|
||||||
pub async fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str> {
|
pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str> {
|
||||||
let mut connection = db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
// account verification
|
// account verification
|
||||||
let (user_id_val, _account_id_val): (i64, i64);
|
let (user_id_val, _account_id_val): (i32, i32);
|
||||||
|
|
||||||
// password argument is used for the level, so
|
// password argument is used for the level, so
|
||||||
match helpers::accounts::auth(input.accountID.clone(), None, input.gjp.clone(), input.gjp2.clone()).await {
|
match helpers::accounts::auth(input.accountID.clone(), None, input.gjp.clone(), input.gjp2.clone()) {
|
||||||
Ok((user_id, account_id)) => {
|
Ok((user_id, account_id)) => {
|
||||||
user_id_val = user_id;
|
user_id_val = user_id;
|
||||||
_account_id_val = account_id;
|
_account_id_val = account_id;
|
||||||
|
@ -89,93 +91,112 @@ pub async fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'stat
|
||||||
let objects_val = level_objects.len();
|
let objects_val = level_objects.len();
|
||||||
let two_player_val = if inner_level_string.get("kA10").unwrap_or(&String::from("0")).parse::<i32>().expect("kA10 not int") == 1 { 1 } else { 0 };
|
let two_player_val = if inner_level_string.get("kA10").unwrap_or(&String::from("0")).parse::<i32>().expect("kA10 not int") == 1 { 1 } else { 0 };
|
||||||
let level_length_val = helpers::levels::secs_to_time(level_length_secs);
|
let level_length_val = helpers::levels::secs_to_time(level_length_secs);
|
||||||
|
|
||||||
// blocking coins
|
// blocking coins
|
||||||
if coins_val > 3 {
|
if coins_val > 3 {
|
||||||
return status::Custom(Status::Ok, "-1")
|
return status::Custom(Status::Ok, "-1")
|
||||||
}
|
}
|
||||||
|
|
||||||
// too many objects
|
// too many objects
|
||||||
let max_objects = config::config_get_with_default("levels.max_objects", 0) as usize;
|
if objects_val > CONFIG.levels.max_objects as usize {
|
||||||
if max_objects != 0 && objects_val > max_objects {
|
|
||||||
return status::Custom(Status::Ok, "-1")
|
return status::Custom(Status::Ok, "-1")
|
||||||
}
|
}
|
||||||
|
|
||||||
// forbidden object checking
|
// forbidden object checking
|
||||||
if let Some(_obj) = level_objects.iter().find(|obj| config::config_get_with_default("levels.blocklist", Vec::new() as Vec<i32>).contains(&obj.id())) {
|
if let Some(_forbidden_object) = level_objects.iter().find(|obj| crate::CONFIG.levels.blocklist.contains(&obj.id())) {
|
||||||
return status::Custom(Status::Ok, "-1")
|
return status::Custom(Status::Ok, "-1")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACE vulnerability check
|
// ACE vulnerability check
|
||||||
for obj in level_objects.iter().filter(|obj| obj.item_block_id().is_some()) {
|
if let Some(_ace_object) = level_objects.iter().find(|obj| obj.item_block_id() < Some(0) || obj.item_block_id() > Some(1100)) {
|
||||||
if obj.item_block_id() < Some(0) || obj.item_block_id() > Some(1100) {
|
return status::Custom(Status::Ok, "-1")
|
||||||
return status::Custom(Status::Ok, "-1");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sqlx::query_scalar!("SELECT COUNT(*) FROM levels WHERE id = ?", input.levelID)
|
// data base 🤣😁
|
||||||
.fetch_one(&mut connection)
|
use crate::models::{Level, NewLevel};
|
||||||
.await
|
|
||||||
.expect("error getting level count") > 0 {
|
|
||||||
// update level
|
|
||||||
|
|
||||||
let level_user_id = sqlx::query!("SELECT user_id FROM levels WHERE id = ?", input.levelID)
|
{
|
||||||
.fetch_one(&mut connection)
|
use crate::schema::levels::dsl::*;
|
||||||
.await
|
|
||||||
.expect("error getting level user id")
|
|
||||||
.user_id;
|
|
||||||
|
|
||||||
if level_user_id != user_id_val {
|
if levels
|
||||||
return status::Custom(Status::Ok, "-1")
|
.filter(id.eq(input.levelID))
|
||||||
|
.count()
|
||||||
|
.get_result::<i64>(connection)
|
||||||
|
.expect("couldnt get count of levels") > 0 {
|
||||||
|
// update level
|
||||||
|
let level_user_id = levels
|
||||||
|
.filter(id.eq(input.levelID))
|
||||||
|
.select(user_id)
|
||||||
|
.get_result::<i32>(connection)
|
||||||
|
.expect("couldnt query levels");
|
||||||
|
|
||||||
|
if level_user_id != user_id_val {
|
||||||
|
return status::Custom(Status::Ok, "-1")
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated_level = diesel::update(levels)
|
||||||
|
.filter(id.eq(input.levelID))
|
||||||
|
.set((
|
||||||
|
description.eq(description_val.chars().take(140).collect::<String>()),
|
||||||
|
password.eq(input.password.clone()),
|
||||||
|
requested_stars.eq(match input.requestedStars {
|
||||||
|
Some(requested_stars_val) => requested_stars_val.clamp(0, 10),
|
||||||
|
None => 0
|
||||||
|
}),
|
||||||
|
version.eq(input.levelVersion),
|
||||||
|
extra_data.eq(extra_string.as_bytes().to_owned()),
|
||||||
|
level_info.eq(input.levelInfo.clone().into_bytes()),
|
||||||
|
editor_time.eq(input.wt.unwrap_or(0)),
|
||||||
|
editor_time_copies.eq(input.wt2.unwrap_or(0)),
|
||||||
|
song_id.eq(song_id_val),
|
||||||
|
length.eq(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");
|
||||||
|
|
||||||
|
fs::write(format!("{}/levels/{}.lvl", crate::CONFIG.db.data_folder, updated_level.id), general_purpose::URL_SAFE.decode(input.levelString.clone()).expect("user provided invalid level string")).expect("couldnt write level to file");
|
||||||
|
|
||||||
|
return status::Custom(Status::Ok, Box::leak(input.levelID.to_string().into_boxed_str()))
|
||||||
|
} else {
|
||||||
|
// upload level
|
||||||
|
let new_level = NewLevel {
|
||||||
|
name: helpers::clean::clean_basic(&input.levelName).chars().take(20).collect(),
|
||||||
|
user_id: user_id_val,
|
||||||
|
description: description_val.chars().take(140).collect(),
|
||||||
|
original: input.original,
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_level = diesel::insert_into(levels)
|
||||||
|
.values(&new_level)
|
||||||
|
.get_result::<Level, >(connection)
|
||||||
|
.expect("failed to insert level");
|
||||||
|
|
||||||
|
fs::write(format!("{}/levels/{}.lvl", crate::CONFIG.db.data_folder, inserted_level.id), general_purpose::URL_SAFE.decode(input.levelString.clone()).expect("user provided invalid level string")).expect("couldnt write level to file");
|
||||||
|
|
||||||
|
return status::Custom(Status::Ok, "1")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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);
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,6 +2,8 @@ use rocket::form::Form;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::status;
|
use rocket::response::status;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::db;
|
use crate::db;
|
||||||
|
|
||||||
|
@ -12,21 +14,31 @@ pub struct FormGetUsers {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/getGJUsers20.php", data = "<input>")]
|
#[post("/getGJUsers20.php", data = "<input>")]
|
||||||
pub async fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str> {
|
pub fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str> {
|
||||||
let mut connection = db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
let username = input.str.to_owned() + "%";
|
// query users
|
||||||
let offset = input.page * 10;
|
use crate::schema::users::dsl::*;
|
||||||
|
use crate::models::User;
|
||||||
|
|
||||||
let query_results = sqlx::query!("SELECT * FROM users WHERE id = ? OR username LIKE ? ORDER BY stars DESC LIMIT 10 OFFSET ?", input.str, username, offset)
|
let mut query_users = users.into_boxed();
|
||||||
.fetch_all(&mut connection)
|
|
||||||
.await
|
match input.str.parse::<i32>() {
|
||||||
.expect("Fatal error loading users");
|
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 mut results: Vec<String> = vec![];
|
let mut results: Vec<String> = vec![];
|
||||||
|
|
||||||
for result in query_results {
|
for result in {
|
||||||
let user = result;
|
query_users
|
||||||
|
.order(stars.desc())
|
||||||
|
.offset(input.page * 10)
|
||||||
|
.limit(10)
|
||||||
|
.get_results::<User, >(connection)
|
||||||
|
.expect("Fatal error loading users")
|
||||||
|
} {
|
||||||
|
let user: User = result;
|
||||||
|
|
||||||
let formatted_result = helpers::format::format(hashmap! {
|
let formatted_result = helpers::format::format(hashmap! {
|
||||||
1 => user.username,
|
1 => user.username,
|
||||||
|
@ -63,15 +75,22 @@ pub async fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str
|
||||||
results.push(formatted_result)
|
results.push(formatted_result)
|
||||||
};
|
};
|
||||||
|
|
||||||
let amount = sqlx::query_scalar!("SELECT COUNT(*) FROM users WHERE id = ? OR username LIKE ?", input.str, username)
|
let mut query_users_count = users.into_boxed();
|
||||||
.fetch_one(&mut connection)
|
|
||||||
.await
|
match input.str.parse::<i32>() {
|
||||||
.expect("error loading users");
|
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 response = if results.is_empty() {
|
let response = if results.is_empty() {
|
||||||
String::from("-1")
|
String::from("-1")
|
||||||
} else {
|
} else {
|
||||||
vec![results.join("|"), format!("{}:{}:10", amount, offset)].join("#")
|
vec![results.join("|"), format!("{}:{}:10", amount, input.page * 10)].join("#")
|
||||||
};
|
};
|
||||||
|
|
||||||
return status::Custom(Status::Ok, Box::leak(response.into_boxed_str()))
|
return status::Custom(Status::Ok, Box::leak(response.into_boxed_str()))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
use diesel::prelude::*;
|
||||||
use password_auth::verify_password;
|
use password_auth::verify_password;
|
||||||
|
|
||||||
use crate::db;
|
use crate::{db, helpers};
|
||||||
use crate::helpers;
|
|
||||||
|
|
||||||
// returns userid, accountid
|
// returns userid, accountid
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
|
@ -9,36 +9,39 @@ pub enum AuthError {
|
||||||
AccountNotFound
|
AccountNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth(account_id: i64, password_val: Option<String>, gjp_val: Option<String>, gjp2_val: Option<String>) -> Result<(i64, i64), AuthError> {
|
pub fn auth(account_id: i32, password_val: Option<String>, gjp_val: Option<String>, gjp2_val: Option<String>) -> Result<(i32, i32), AuthError> {
|
||||||
let connection = &mut db::establish_sqlite_conn().await;
|
use crate::schema::accounts::dsl::*;
|
||||||
|
|
||||||
let query_result = sqlx::query!("SELECT password, gjp2 FROM accounts WHERE id = ?", account_id)
|
let connection = &mut db::establish_connection_pg();
|
||||||
.fetch_one(connection)
|
|
||||||
.await;
|
let query_result = accounts
|
||||||
|
.select((password, gjp2))
|
||||||
|
.filter(id.eq(account_id))
|
||||||
|
.get_result::<(String, String)>(connection);
|
||||||
|
|
||||||
match query_result {
|
match query_result {
|
||||||
Ok(result) => {
|
Ok((
|
||||||
let password_queried_val = result.password;
|
password_queried_val,
|
||||||
let gjp2_queried_val = result.gjp2;
|
gjp2_queried_val
|
||||||
|
)) => {
|
||||||
match password_val {
|
match password_val {
|
||||||
Some(password_val) => {
|
Some(password_val) => {
|
||||||
match verify_password(password_val, &password_queried_val) {
|
match verify_password(password_val, &password_queried_val) {
|
||||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
|
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
|
||||||
Err(_) => return Err(AuthError::WrongPassword)
|
Err(_) => return Err(AuthError::WrongPassword)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => match gjp_val {
|
None => match gjp_val {
|
||||||
Some(gjp_val) => {
|
Some(gjp_val) => {
|
||||||
match verify_password(helpers::encryption::decode_gjp(gjp_val), &password_queried_val) {
|
match verify_password(helpers::encryption::decode_gjp(gjp_val), &password_queried_val) {
|
||||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
|
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
|
||||||
Err(_) => return Err(AuthError::WrongPassword)
|
Err(_) => return Err(AuthError::WrongPassword)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => match gjp2_val {
|
None => match gjp2_val {
|
||||||
Some(gjp2_val) => {
|
Some(gjp2_val) => {
|
||||||
match verify_password(gjp2_val, &gjp2_queried_val) {
|
match verify_password(gjp2_val, &gjp2_queried_val) {
|
||||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
|
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
|
||||||
Err(_) => return Err(AuthError::WrongPassword)
|
Err(_) => return Err(AuthError::WrongPassword)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -53,14 +56,16 @@ pub async fn auth(account_id: i64, password_val: Option<String>, gjp_val: Option
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user_id_from_account_id(ext_id: i64) -> i64 {
|
pub fn get_user_id_from_account_id(ext_id: i32) -> i32 {
|
||||||
let connection = &mut db::establish_sqlite_conn().await;
|
use crate::schema::users::dsl::*;
|
||||||
|
|
||||||
let user_id = sqlx::query!("SELECT id FROM users WHERE account_id = ?", ext_id)
|
let connection = &mut db::establish_connection_pg();
|
||||||
.fetch_one(connection)
|
|
||||||
.await
|
let user_id = users
|
||||||
.expect("no user associated with account id??")
|
.filter(udid.eq(ext_id.to_string()).or(account_id.eq(ext_id)))
|
||||||
.id;
|
.select(id)
|
||||||
|
.get_result::<i32>(connection)
|
||||||
|
.expect("No user associated with account?!?!?");
|
||||||
|
|
||||||
return user_id
|
return user_id
|
||||||
}
|
}
|
|
@ -8,9 +8,4 @@ pub fn clean_no_space(string: &str) -> String {
|
||||||
pub fn clean_basic(string: &str) -> String {
|
pub fn clean_basic(string: &str) -> String {
|
||||||
let regex = Regex::new(r"[^A-Za-z0-9\-_ ]").unwrap();
|
let regex = Regex::new(r"[^A-Za-z0-9\-_ ]").unwrap();
|
||||||
return regex.replace_all(string, "").to_string();
|
return regex.replace_all(string, "").to_string();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clean_char(string: &str) -> String {
|
|
||||||
let regex = Regex::new(r"[^A-Za-z0-9 ]").unwrap();
|
|
||||||
return regex.replace_all(string, "").to_string();
|
|
||||||
}
|
}
|
|
@ -10,7 +10,7 @@ pub enum LevelDifficulty {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LevelDifficulty {
|
impl LevelDifficulty {
|
||||||
pub fn new(value: i64) -> LevelDifficulty {
|
pub fn new(value: i32) -> LevelDifficulty {
|
||||||
match value {
|
match value {
|
||||||
0 => LevelDifficulty::Auto,
|
0 => LevelDifficulty::Auto,
|
||||||
1 => LevelDifficulty::Easy,
|
1 => LevelDifficulty::Easy,
|
||||||
|
@ -23,7 +23,7 @@ impl LevelDifficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(self) -> i64 {
|
pub fn value(self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
LevelDifficulty::Auto => 0,
|
LevelDifficulty::Auto => 0,
|
||||||
LevelDifficulty::Easy => 1,
|
LevelDifficulty::Easy => 1,
|
||||||
|
@ -35,7 +35,7 @@ impl LevelDifficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_star_difficulty(self) -> i64 {
|
pub fn to_star_difficulty(self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
LevelDifficulty::Auto => 5,
|
LevelDifficulty::Auto => 5,
|
||||||
LevelDifficulty::Easy => 1,
|
LevelDifficulty::Easy => 1,
|
||||||
|
@ -47,7 +47,7 @@ impl LevelDifficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stars_to_diff(stars: i64) -> Self {
|
pub fn stars_to_diff(stars: i32) -> Self {
|
||||||
match stars {
|
match stars {
|
||||||
1 => LevelDifficulty::Auto,
|
1 => LevelDifficulty::Auto,
|
||||||
2 => LevelDifficulty::Easy,
|
2 => LevelDifficulty::Easy,
|
||||||
|
@ -70,7 +70,7 @@ pub enum DemonDifficulty {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DemonDifficulty {
|
impl DemonDifficulty {
|
||||||
pub fn new(value: i64) -> DemonDifficulty {
|
pub fn new(value: i32) -> DemonDifficulty {
|
||||||
match value {
|
match value {
|
||||||
0 => DemonDifficulty::Easy,
|
0 => DemonDifficulty::Easy,
|
||||||
1 => DemonDifficulty::Medium,
|
1 => DemonDifficulty::Medium,
|
||||||
|
@ -81,7 +81,7 @@ impl DemonDifficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(self) -> i64 {
|
pub fn value(self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
DemonDifficulty::Easy => 0,
|
DemonDifficulty::Easy => 0,
|
||||||
DemonDifficulty::Medium => 1,
|
DemonDifficulty::Medium => 1,
|
||||||
|
@ -91,7 +91,7 @@ impl DemonDifficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_demon_difficulty(self) -> i64 {
|
pub fn to_demon_difficulty(self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
DemonDifficulty::Easy => 3,
|
DemonDifficulty::Easy => 3,
|
||||||
DemonDifficulty::Medium => 4,
|
DemonDifficulty::Medium => 4,
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub fn decode_gjp(gjp: String) -> String {
|
||||||
return xor_decoded
|
return xor_decoded
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_multi(level_hash_data: Vec<(i64, i32, bool)>) -> String {
|
pub fn gen_multi(level_hash_data: Vec<(i32, i32, bool)>) -> String {
|
||||||
let mut input_str = String::new();
|
let mut input_str = String::new();
|
||||||
|
|
||||||
for (_index, val) in level_hash_data.iter().enumerate() {
|
for (_index, val) in level_hash_data.iter().enumerate() {
|
||||||
|
|
|
@ -195,19 +195,8 @@ pub fn decode(level_data: String) -> Vec<HashMap<String, String>> {
|
||||||
|
|
||||||
let mut decoder = GzDecoder::new(&decoded_bytes[..]);
|
let mut decoder = GzDecoder::new(&decoded_bytes[..]);
|
||||||
|
|
||||||
let uncompressed_data = String::from_utf8(if decoded_bytes.starts_with(&[0x1F, 0x8B]) {
|
let mut uncompressed_data = String::new();
|
||||||
// gzip!!
|
decoder.read_to_string(&mut uncompressed_data).expect("err unzipping level");
|
||||||
let mut decompressed_data = Vec::new();
|
|
||||||
decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level");
|
|
||||||
decompressed_data
|
|
||||||
} else if decoded_bytes.starts_with(&[0x78]) {
|
|
||||||
// zlib!!
|
|
||||||
let mut decompressed_data = Vec::new();
|
|
||||||
decoder.read_to_end(&mut decompressed_data).expect("err uncompressing level");
|
|
||||||
decompressed_data
|
|
||||||
} else {
|
|
||||||
panic!("invalid compression method")
|
|
||||||
}).expect("invalid utf-8 sequence");
|
|
||||||
|
|
||||||
return parse(uncompressed_data.as_str())
|
return parse(uncompressed_data.as_str())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,59 @@
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::db;
|
use crate::db;
|
||||||
|
|
||||||
pub const REUPLOAD_USER_NAME: &str = "reupload";
|
pub const REUPLOAD_USER_NAME: &str = "reupload";
|
||||||
|
|
||||||
pub static REUPLOAD_ACCOUNT_ID: RwLock<i64> = RwLock::new(0);
|
pub static REUPLOAD_ACCOUNT_ID: RwLock<i32> = RwLock::new(0);
|
||||||
|
|
||||||
pub async fn init() {
|
pub fn init() {
|
||||||
let mut connection = db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
let result = sqlx::query!("SELECT id FROM accounts WHERE username = ?", REUPLOAD_USER_NAME)
|
use crate::schema::{accounts, users};
|
||||||
.fetch_one(&mut connection)
|
use crate::models::{Account, NewAccount, User, NewUser};
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
match accounts::table
|
||||||
Ok(result) => {
|
.filter(accounts::username.eq(REUPLOAD_USER_NAME))
|
||||||
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock");
|
.select(accounts::id)
|
||||||
*write_lock = result.id;
|
.get_result::<i32, >(connection) {
|
||||||
},
|
Ok(reupload_acc_id) => {
|
||||||
Err(_) => {
|
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock!!");
|
||||||
let new_account = sqlx::query!(
|
*write_lock = reupload_acc_id;
|
||||||
"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 = new_account.last_insert_rowid() as i64;
|
println!("reupload account found, id: {}", reupload_acc_id);
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
let new_account = NewAccount {
|
||||||
|
username: REUPLOAD_USER_NAME.to_string(),
|
||||||
|
gjp2: "!".to_string(),
|
||||||
|
password: "!".to_string(),
|
||||||
|
email: "".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
sqlx::query!(
|
let inserted_account = diesel::insert_into(accounts::table)
|
||||||
"INSERT INTO users (account_id, username, registered) VALUES (?, ?, ?)",
|
.values(&new_account)
|
||||||
reupload_acc_id,
|
.get_result::<Account, >(connection)
|
||||||
REUPLOAD_USER_NAME,
|
.expect("Fatal error saving the new account");
|
||||||
1
|
|
||||||
)
|
|
||||||
.execute(&mut connection)
|
|
||||||
.await
|
|
||||||
.expect("error saving the new user");
|
|
||||||
|
|
||||||
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock");
|
let reupload_acc_id = inserted_account.id;
|
||||||
*write_lock = reupload_acc_id;
|
|
||||||
|
|
||||||
println!("created reupload account, id: {}", reupload_acc_id);
|
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)
|
||||||
|
.expect("Fatal error saving the new user");
|
||||||
|
|
||||||
|
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock!!");
|
||||||
|
*write_lock = reupload_acc_id;
|
||||||
|
|
||||||
|
println!("created reupload account, id: {}", reupload_acc_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
31
src/main.rs
31
src/main.rs
|
@ -12,11 +12,20 @@ use rocket::data::{Limits, ToByteUnit};
|
||||||
|
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
mod config;
|
|
||||||
mod db;
|
mod db;
|
||||||
mod endpoints;
|
use db::*;
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
use helpers::*;
|
||||||
|
|
||||||
|
mod endpoints;
|
||||||
|
use endpoints::*;
|
||||||
|
|
||||||
mod template_endpoints;
|
mod template_endpoints;
|
||||||
|
use template_endpoints::*;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
use config::*;
|
||||||
|
|
||||||
#[get("/<file..>")]
|
#[get("/<file..>")]
|
||||||
async fn files(file: PathBuf) -> Option<NamedFile> {
|
async fn files(file: PathBuf) -> Option<NamedFile> {
|
||||||
|
@ -24,19 +33,20 @@ async fn files(file: PathBuf) -> Option<NamedFile> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[launch]
|
#[launch]
|
||||||
async fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
// init stuff
|
// init stuff
|
||||||
crate::helpers::reupload::init().await;
|
crate::helpers::reupload::init();
|
||||||
|
|
||||||
// data directories
|
// data directories
|
||||||
// unhardcore this maybe?
|
// this is a bit scuffed
|
||||||
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(&CONFIG.db.data_folder).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");
|
fs::create_dir_all(format!("{}/levels", &CONFIG.db.data_folder)).expect("failed to create data directory for levels");
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
// conf
|
// conf
|
||||||
.configure(rocket::Config::figment()
|
.configure(rocket::Config::figment()
|
||||||
.merge(("port", config::config_get_with_default("general.port", 8000)))
|
.merge(("port", CONFIG.general.port))
|
||||||
|
.merge(("ip_header", CONFIG.general.realip_header.as_str()))
|
||||||
.merge(("limits", Limits::new().limit("forms", 10.megabytes()))))
|
.merge(("limits", Limits::new().limit("forms", 10.megabytes()))))
|
||||||
// actual website
|
// actual website
|
||||||
.mount("/", routes![
|
.mount("/", routes![
|
||||||
|
@ -57,9 +67,10 @@ async fn rocket() -> _ {
|
||||||
files
|
files
|
||||||
])
|
])
|
||||||
// https://www.youtube.com/watch?v=_pLrtsf5yfE
|
// https://www.youtube.com/watch?v=_pLrtsf5yfE
|
||||||
.mount(format!("/{}", config::config_get_with_default("general.append_path", "".to_string())), routes![
|
.mount(CONFIG.general.append_path.as_str(), routes![
|
||||||
endpoints::accounts::login_account::login_account,
|
endpoints::accounts::login_account::login_account,
|
||||||
endpoints::accounts::register_account::register_account,
|
endpoints::accounts::register_account::register_account,
|
||||||
|
endpoints::accounts::update_account_settings::update_account_settings,
|
||||||
|
|
||||||
endpoints::users::get_users::get_users,
|
endpoints::users::get_users::get_users,
|
||||||
|
|
||||||
|
@ -67,6 +78,6 @@ async fn rocket() -> _ {
|
||||||
endpoints::levels::get_levels::get_levels,
|
endpoints::levels::get_levels::get_levels,
|
||||||
endpoints::levels::upload_level::upload_level
|
endpoints::levels::upload_level::upload_level
|
||||||
])
|
])
|
||||||
// so templates work
|
// so templates work i think
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
}
|
}
|
|
@ -4,20 +4,25 @@ use rocket_dyn_templates::{Template, context};
|
||||||
|
|
||||||
use rocket::http::CookieJar;
|
use rocket::http::CookieJar;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::db;
|
use crate::db;
|
||||||
|
|
||||||
#[get("/accounts")]
|
#[get("/accounts")]
|
||||||
pub async fn account_management(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
|
pub fn account_management(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
|
||||||
let connection = &mut db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
let logged_in = crate::helpers::templates::authenticate(cookies);
|
let logged_in = crate::helpers::templates::authenticate(cookies);
|
||||||
|
|
||||||
match logged_in {
|
match logged_in {
|
||||||
Ok((username_val, _account_id, user_id)) => {
|
Ok((username_val, _account_id_val, user_id_val)) => {
|
||||||
let result = sqlx::query!("SELECT stars, demons, coins, user_coins, diamonds, creator_points FROM users WHERE id = ?", user_id)
|
use crate::schema::users::dsl::*;
|
||||||
.fetch_one(connection)
|
use crate::models::User;
|
||||||
.await
|
|
||||||
.expect("couldnt query database");
|
let result = users
|
||||||
|
.filter(id.eq(user_id_val))
|
||||||
|
.get_result::<User, >(connection)
|
||||||
|
.expect("couldnt find user with user id from account");
|
||||||
|
|
||||||
return Ok(Template::render("account_management", context! {
|
return Ok(Template::render("account_management", context! {
|
||||||
username: username_val,
|
username: username_val,
|
||||||
|
|
|
@ -8,6 +8,8 @@ use rocket::http::{Cookie, CookieJar};
|
||||||
|
|
||||||
use rocket::time::Duration;
|
use rocket::time::Duration;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
|
|
||||||
|
@ -18,23 +20,25 @@ pub struct FormLogin {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/login", data = "<input>")]
|
#[post("/login", data = "<input>")]
|
||||||
pub async fn post_login(cookies: &CookieJar<'_>, input: Form<FormLogin>) -> Template {
|
pub fn post_login(cookies: &CookieJar<'_>, input: Form<FormLogin>) -> Template {
|
||||||
let connection = &mut db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
let result = sqlx::query!("SELECT id, username FROM accounts WHERE username = ?", input.username)
|
use crate::schema::accounts::dsl::*;
|
||||||
.fetch_one(connection)
|
|
||||||
.await;
|
let result = accounts
|
||||||
|
.select((id, username))
|
||||||
|
.filter(username.eq(input.username.clone()))
|
||||||
|
.get_result::<(i32, String), >(connection);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(result) => {
|
Ok(account_id_username_val) => {
|
||||||
let account_username = result.username;
|
match helpers::accounts::auth(account_id_username_val.0, Some(input.password.clone()), None, None) {
|
||||||
|
|
||||||
match helpers::accounts::auth(result.id, Some(input.password.clone()), None, None).await {
|
|
||||||
Ok(account_id_user_id_val) => {
|
Ok(account_id_user_id_val) => {
|
||||||
cookies.add_private(Cookie::build(
|
cookies.add_private(Cookie::build(
|
||||||
"blackmail_data",
|
"blackmail_data",
|
||||||
format!("{}:{}:{}", account_username, result.id, account_id_user_id_val.1))
|
format!("{}:{}:{}", account_id_username_val.1, account_id_user_id_val.0, account_id_user_id_val.1))
|
||||||
.path("/")
|
.path("/")
|
||||||
|
// should probably make this true when we get into production
|
||||||
.secure(false)
|
.secure(false)
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.max_age(Duration::days(365))
|
.max_age(Duration::days(365))
|
||||||
|
|
|
@ -12,9 +12,10 @@ use std::fs;
|
||||||
|
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::config;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct LevelResults {
|
struct LevelResults {
|
||||||
|
@ -36,9 +37,9 @@ pub struct FormReupload {
|
||||||
|
|
||||||
#[post("/tools/reupload", data = "<input>")]
|
#[post("/tools/reupload", data = "<input>")]
|
||||||
pub async fn post_reupload(input: Form<FormReupload>) -> Template {
|
pub async fn post_reupload(input: Form<FormReupload>) -> Template {
|
||||||
let connection = &mut db::establish_sqlite_conn().await;
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
let disabled = !config::config_get_with_default("levels.reupload", true);
|
let disabled = !crate::CONFIG.levels.reupload;
|
||||||
|
|
||||||
if !disabled {
|
if !disabled {
|
||||||
let remote_level_id = input.level_id;
|
let remote_level_id = input.level_id;
|
||||||
|
@ -68,64 +69,46 @@ 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 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"));
|
let level_data = helpers::levels::gmd_parse(&gmd_file.text().await.expect("failed to parse gmd file as text"));
|
||||||
|
|
||||||
|
use crate::schema::levels::dsl::*;
|
||||||
|
use crate::models::{Level, NewLevel};
|
||||||
|
|
||||||
let level_name = level_data.get("k2").expect("level name not found").to_string();
|
let new_level = NewLevel {
|
||||||
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)");
|
name: level_data.get("k2").expect("level name not found").to_string(),
|
||||||
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)");
|
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)"),
|
||||||
let level_game_version = level_data.get("k17").expect("level game version not found").to_string().parse::<i32>().expect("level game version not int");
|
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_binary_version = level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::<i32>().expect("level binary version not int");
|
original: None,
|
||||||
let level_password = level_data.get("k41").expect("level password not found").to_string();
|
game_version: level_data.get("k17").expect("level game version not found").to_string().parse::<i32>().expect("level game version not int"),
|
||||||
let level_requested_stars = level_data.get("k66").expect("level requested stars not found").to_string().parse::<i32>().expect("level requested stars not int");
|
binary_version: level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::<i32>().expect("level binary version not int"),
|
||||||
let level_version = level_data.get("k16").expect("level version not found").to_string().parse::<i32>().expect("level version not int");
|
password: Some(level_data.get("k41").expect("level password not found").to_string()),
|
||||||
let extra_string = level_data.get("extra_string").unwrap_or(&crate::helpers::levels::DEFAULT_EXTRA_STRING).to_string().into_bytes();
|
requested_stars: level_data.get("k66").expect("level requested stars not found").to_string().parse::<i32>().expect("level requested stars not int"),
|
||||||
let default_level_info = crate::helpers::levels::DEFAULT_LEVEL_INFO.to_string().into_bytes();
|
unlisted: 0,
|
||||||
let level_editor_time = level_data.get("k80").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time not int");
|
version: level_data.get("k16").expect("level version not found").to_string().parse::<i32>().expect("level version 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");
|
extra_data: level_data.get("extra_string").unwrap_or(&crate::helpers::levels::DEFAULT_EXTRA_STRING).to_string().into_bytes(),
|
||||||
let level_song_id = if level_data.get("k8").unwrap_or(&String::from("0")).parse::<i32>().expect("level song id not int") == 0 {
|
level_info: crate::helpers::levels::DEFAULT_LEVEL_INFO.to_string().into_bytes(),
|
||||||
level_data.get("k45").expect("level song id doesnt fucking exist").parse::<i32>().expect("level song id not int")
|
editor_time: level_data.get("k80").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time not int"),
|
||||||
} else {
|
editor_time_copies: level_data.get("k81").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time copies not int"),
|
||||||
level_data.get("k8").expect("level song id doesnt fucking exist").parse::<i32>().expect("level song id not int")
|
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 = sqlx::query!(
|
let inserted_level = diesel::insert_into(levels)
|
||||||
"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(&new_level)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
.get_result::<Level, >(connection)
|
||||||
level_name,
|
.expect("failed to insert level");
|
||||||
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");
|
|
||||||
|
|
||||||
// sqlite doesnt have return clause :frown: maybe swap to custom id system
|
fs::write(format!("{}/levels/{}.lvl", crate::CONFIG.db.data_folder, 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");
|
||||||
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! {
|
return Template::render("reupload", context! {
|
||||||
level_id: inserted_id
|
level_id: inserted_level.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +119,7 @@ pub async fn post_reupload(input: Form<FormReupload>) -> Template {
|
||||||
|
|
||||||
#[get("/tools/reupload")]
|
#[get("/tools/reupload")]
|
||||||
pub fn get_reupload() -> Template {
|
pub fn get_reupload() -> Template {
|
||||||
let disabled = !config::config_get_with_default("levels.reupload", true);
|
let disabled = !crate::CONFIG.levels.reupload;
|
||||||
|
|
||||||
Template::render("reupload", context! {
|
Template::render("reupload", context! {
|
||||||
disabled: disabled
|
disabled: disabled
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue