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
|
||||
|
||||
/data
|
||||
*.db
|
||||
|
||||
.env
|
||||
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]
|
||||
base64 = "0.21.3"
|
||||
diesel = { version = "=2.1.0", features = ["postgres", "64-column-tables"] }
|
||||
dotenvy = "0.15.7"
|
||||
flate2 = "1.0.27"
|
||||
maplit = "1.0.2"
|
||||
migrate = "0.2.0"
|
||||
password-auth = "0.3.0"
|
||||
rand = "0.8.5"
|
||||
regex = "1.9.4"
|
||||
|
@ -19,6 +19,4 @@ roxmltree = "0.18.0"
|
|||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
sha = "1.0.3"
|
||||
sqlx = { version = "0.7.2", features = ["runtime-tokio", "sqlite"] }
|
||||
tempfile = "3.8.0"
|
||||
toml = "0.7.6"
|
||||
|
|
5
build.rs
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/
|
||||
# example.com/aaaaaaaaaa/
|
||||
# ^^^^^^^^^^^
|
||||
# leaving as empty will disable this
|
||||
append_path = ""
|
||||
# where can your server be accessible (port)?
|
||||
# leaving as "/" will disable this
|
||||
append_path = "/"
|
||||
# where can your server be accessible?
|
||||
port = 8000
|
||||
# where can your server be accessible (domain)?
|
||||
# this is used for patching the executable
|
||||
#
|
||||
# example:
|
||||
# boomlings.com
|
||||
# localhost:8000
|
||||
domain = "localhost:8000"
|
||||
# your realip header, if you're behind a reverse proxy
|
||||
realip_header = "X-Real-IP"
|
||||
|
||||
[accounts]
|
||||
# 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 (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
CREATE COLLATION case_insensitive (
|
||||
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)
|
||||
password TEXT NOT NULL, -- argon2 hashed (rubrub uses bcrypt but oh well)
|
||||
email VARCHAR(254) NOT NULL,
|
||||
|
@ -19,5 +25,5 @@ CREATE TABLE accounts (
|
|||
twitter_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 (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
|
||||
-- on a registered account, account_id refers to the
|
||||
-- account ID - however, pre 2.0, instead udid referred
|
||||
-- to the user's UUID or UDID, depending on platform.
|
||||
-- UUID and UDID are unique ids assigned for green
|
||||
-- username users
|
||||
--
|
||||
-- in short, if `registered`, use account_id, else, use udid
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
||||
-- if `registered`, use account_id, else, use udid
|
||||
udid TEXT,
|
||||
account_id INTEGER references accounts(id),
|
||||
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,
|
||||
demons INTEGER NOT NULL DEFAULT 0,
|
||||
|
@ -38,8 +34,8 @@ CREATE TABLE users (
|
|||
special 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')),
|
||||
last_played 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 (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
|
||||
|
||||
is_banned INTEGER NOT NULL DEFAULT 0,
|
||||
is_banned_upload INTEGER NOT NULL DEFAULT 0
|
|
@ -1,11 +1,11 @@
|
|||
CREATE TABLE levels (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
|
||||
modified_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
|
||||
id SERIAL PRIMARY KEY,
|
||||
created_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
|
||||
modified_at TEXT NOT NULL DEFAULT (TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS')),
|
||||
|
||||
name VARCHAR(20) NOT NULL,
|
||||
user_id INTEGER NOT NULL references users(id),
|
||||
description VARCHAR(140) NOT NULL DEFAULT "",
|
||||
description VARCHAR(140) NOT NULL DEFAULT '',
|
||||
original INTEGER,
|
||||
|
||||
game_version INTEGER NOT NULL,
|
||||
|
@ -16,8 +16,8 @@ CREATE TABLE levels (
|
|||
unlisted INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
version INTEGER NOT NULL DEFAULT 0,
|
||||
extra_data BLOB NOT NULL,
|
||||
level_info BLOB NOT NULL,
|
||||
extra_data BYTEA NOT NULL,
|
||||
level_info BYTEA NOT NULL,
|
||||
|
||||
editor_time INTEGER NOT NULL,
|
||||
editor_time_copies INTEGER NOT NULL,
|
||||
|
@ -39,4 +39,4 @@ CREATE TABLE levels (
|
|||
featured INTEGER NOT NULL DEFAULT 0,
|
||||
epic 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?
|
||||
|
||||
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
|
||||
|
||||
|
@ -17,40 +17,29 @@ i'm trying to learn some rust, and this is a solid choice. most GDPS solutions o
|
|||
|
||||
## 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`
|
||||
|
||||
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.
|
||||
- run `cargo build --release`
|
||||
|
||||
## todo
|
||||
|
||||
- add get levels search back (find how to easily do dynamic queries)
|
||||
- user icons in account management pages
|
||||
- account settings page
|
||||
- better web design (make formatting more consistant)
|
||||
- use chrono for dates in database
|
||||
- 2.2's friends only unlisted
|
||||
- dailies, weeklies, events(?)
|
||||
- better web design
|
||||
- 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
|
||||
- use chrono
|
||||
- 2.2 friends only unlisted
|
||||
- add dailies, events, weekly
|
||||
- moderation utilities
|
||||
- better song support
|
||||
- cache authentication (redis or mem)
|
||||
- make a proper rank system (reuploading, uploading music, rating, etc.)
|
||||
- use serde to make the forms whateverCaseThisIs rather than breaking our lint convention
|
||||
- add back `realip` header support
|
||||
- add configurable form limits
|
||||
- nix
|
||||
- clean up difficulty/demon difficulties. It's fucking VILE.
|
||||
- panic less, use results
|
||||
- authentication caching
|
||||
- use log instead of println
|
||||
- make a proper rank system (reuploading, uploading music, rating, etc.)
|
|
@ -1,34 +1,52 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use std::fs;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use toml::Table;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub static CONFIG: LazyLock<Table> = LazyLock::new(|| {
|
||||
let toml_str = fs::read_to_string("config.toml").expect("error finding toml config");
|
||||
let config: Table = toml::from_str(toml_str.as_str()).expect("error parsing toml config");
|
||||
|
||||
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)
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub general: ConfigGeneral,
|
||||
pub accounts: ConfigAccounts,
|
||||
pub db: ConfigDB,
|
||||
pub levels: ConfigLevels
|
||||
}
|
||||
|
||||
pub fn config_get_with_default<'de, T>(key: &str, default: T) -> T
|
||||
where
|
||||
T: DeserializeOwned + 'de
|
||||
{
|
||||
let val = config_get(key)
|
||||
.and_then(|v| v.to_owned().try_into().expect("invalid toml val"))
|
||||
.unwrap_or_else(|| default);
|
||||
#[derive(Deserialize)]
|
||||
pub struct ConfigGeneral {
|
||||
pub append_path: String,
|
||||
pub port: u16,
|
||||
pub realip_header: String
|
||||
}
|
||||
|
||||
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 std::env;
|
||||
|
||||
pub async fn establish_sqlite_conn() -> SqliteConnection {
|
||||
pub mod models;
|
||||
pub mod schema;
|
||||
|
||||
pub fn establish_connection_pg() -> PgConnection {
|
||||
dotenv().ok();
|
||||
|
||||
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 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::response::status;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::helpers;
|
||||
use crate::db;
|
||||
|
||||
|
@ -15,43 +17,46 @@ pub struct FromLoginAccount {
|
|||
}
|
||||
|
||||
#[post("/accounts/loginGJAccount.php", data = "<input>")]
|
||||
pub async fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> {
|
||||
let connection = &mut db::establish_sqlite_conn().await;
|
||||
pub fn login_account(input: Form<FromLoginAccount>) -> status::Custom<&'static str> {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let username = helpers::clean::clean_basic(input.userName.as_ref());
|
||||
|
||||
let password = input.password.clone();
|
||||
let gjp = input.gjp.clone();
|
||||
let gjp2 = input.gjp2.clone();
|
||||
|
||||
if input.userName != username {
|
||||
if input.userName != helpers::clean::clean_no_space(input.userName.as_ref()) {
|
||||
return status::Custom(Status::Ok, "-4")
|
||||
}
|
||||
|
||||
// why does this check exist? it's kinda useless
|
||||
if let Some(password) = password {
|
||||
if password.len() < 6 {
|
||||
return status::Custom(Status::Ok, "-8")
|
||||
}
|
||||
// gjp2 checks dont matter, its hashed, gjp checks would break bc its base64, and why does this check exist if its just for logging in robtop this is useless it doesnt provide security we already did the security on the register account u fucking faggot im really bored of working on this but im also excited to see if it works deepwoken solos mid dash
|
||||
match input.password.clone() {
|
||||
Some(password_val) => {
|
||||
if password_val.len() < 6 {
|
||||
return status::Custom(Status::Ok, "-8")
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
if username.len() < 3 {
|
||||
if input.userName.len() < 3 {
|
||||
return status::Custom(Status::Ok, "-9")
|
||||
}
|
||||
|
||||
let result = sqlx::query_scalar!("SELECT id FROM accounts WHERE username = ?", username)
|
||||
.fetch_one(connection)
|
||||
.await;
|
||||
// account verification
|
||||
{
|
||||
use crate::schema::accounts::dsl::*;
|
||||
|
||||
match result {
|
||||
Ok(account_id_val) => {
|
||||
let user_id_val = helpers::accounts::get_user_id_from_account_id(account_id_val).await;
|
||||
let query_result = accounts
|
||||
.select(id)
|
||||
.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 {
|
||||
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")
|
||||
match query_result {
|
||||
Ok(account_id_val) => {
|
||||
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()) {
|
||||
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::response::status;
|
||||
|
||||
use diesel::prelude::*;
|
||||
use diesel::result::Error;
|
||||
|
||||
use password_auth::generate_hash;
|
||||
|
||||
use crate::config;
|
||||
use crate::CONFIG;
|
||||
use crate::helpers;
|
||||
use crate::db;
|
||||
|
||||
|
@ -16,59 +19,77 @@ pub struct FormRegisterAccount {
|
|||
}
|
||||
|
||||
#[post("/accounts/registerGJAccount.php", data = "<input>")]
|
||||
pub async fn register_account(input: Form<FormRegisterAccount>) -> status::Custom<&'static str> {
|
||||
let mut connection = db::establish_sqlite_conn().await;
|
||||
|
||||
let username = helpers::clean::clean_basic(input.userName.as_ref());
|
||||
let password = input.password.clone();
|
||||
let email = input.email.clone();
|
||||
|
||||
let hashed_password = generate_hash(password.clone());
|
||||
let gjp2 = helpers::encryption::get_gjp2(password.clone());
|
||||
pub fn register_account(input: Form<FormRegisterAccount>) -> status::Custom<&'static str> {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
if config::config_get_with_default("accounts.allow_registration", true) == false {
|
||||
if CONFIG.accounts.allow_registration == false {
|
||||
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")
|
||||
}
|
||||
|
||||
if password.len() < 6 {
|
||||
if input.password.len() < 6 {
|
||||
return status::Custom(Status::Ok, "-8")
|
||||
}
|
||||
|
||||
if username.len() < 3 {
|
||||
if input.userName.len() < 3 {
|
||||
return status::Custom(Status::Ok, "-9")
|
||||
}
|
||||
|
||||
if username.len() > 20 {
|
||||
if input.userName.len() > 20 {
|
||||
return status::Custom(Status::Ok, "-4")
|
||||
}
|
||||
|
||||
if email.len() > 254 {
|
||||
if input.email.len() > 254 {
|
||||
return status::Custom(Status::Ok, "-6")
|
||||
}
|
||||
|
||||
// check if the username is already taken
|
||||
sqlx::query_scalar!("SELECT COUNT(*) FROM accounts WHERE username = ?", username)
|
||||
.fetch_one(&mut connection)
|
||||
.await
|
||||
.map_err(|_| status::Custom(Status::Ok, "-1"))
|
||||
.expect("error getting the account count");
|
||||
// account management
|
||||
use crate::models::{Account, NewAccount};
|
||||
|
||||
let inserted_account = sqlx::query!("INSERT INTO accounts (username, password, email, gjp2) VALUES (?, ?, ?, ?)", username, hashed_password, email, gjp2)
|
||||
.execute(&mut connection)
|
||||
.await
|
||||
.expect("error saving the new account");
|
||||
let inserted_account: 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
|
||||
sqlx::query!("INSERT INTO users (account_id, username, registered) VALUES (?, ?, 1)", inserted_account_id, username)
|
||||
.execute(&mut connection)
|
||||
.await
|
||||
.expect("error saving the new user");
|
||||
use crate::models::{User, NewUser};
|
||||
|
||||
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::response::status;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
|
||||
use flate2::read::{GzDecoder, ZlibDecoder};
|
||||
|
@ -11,7 +13,6 @@ use std::fs;
|
|||
use std::io::prelude::*;
|
||||
|
||||
use crate::helpers;
|
||||
use crate::config;
|
||||
use crate::db;
|
||||
|
||||
#[derive(FromForm)]
|
||||
|
@ -22,11 +23,17 @@ pub struct FormDownloadLevel {
|
|||
}
|
||||
|
||||
#[post("/downloadGJLevel22.php", data = "<input>")]
|
||||
pub async fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static str> {
|
||||
let mut connection = db::establish_sqlite_conn().await;
|
||||
pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static str> {
|
||||
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 query = levels::table.into_boxed();
|
||||
|
||||
match input.levelID {
|
||||
-1 => {
|
||||
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)
|
||||
.fetch_one(&mut connection)
|
||||
.await
|
||||
.expect("error loading levels");
|
||||
// database query
|
||||
{
|
||||
let result = query
|
||||
.filter(levels::id.eq(input.levelID))
|
||||
.get_result::<Level, >(connection)
|
||||
.expect("fatal error loading levels");
|
||||
|
||||
let set_difficulty = match result.difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
let community_difficulty = match result.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 user: User = users::table.find(result.user_id).get_result::<User, >(connection).expect("couldnt get user from lvl");
|
||||
let level: Level = result;
|
||||
|
||||
let set_difficulty = match level.difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
let community_difficulty = match level.community_difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
let difficulty = match set_difficulty {
|
||||
Some(diff) => {
|
||||
Some(diff)
|
||||
},
|
||||
None => {
|
||||
match community_difficulty {
|
||||
Some(diff) => {
|
||||
Some(diff)
|
||||
},
|
||||
None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let demon_difficulty = match result.demon_difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::DemonDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
};
|
||||
let demon_difficulty = match level.demon_difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::DemonDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
|
||||
let xor_pass: String;
|
||||
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"))
|
||||
} 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)
|
||||
let xor_pass: String;
|
||||
if input.gameVersion.unwrap_or(19) >= 20 {
|
||||
xor_pass = general_purpose::URL_SAFE.encode(helpers::encryption::cyclic_xor_string(&level.password.clone().unwrap_or(String::from("0")), "26364"))
|
||||
} else {
|
||||
result.description
|
||||
},
|
||||
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")));
|
||||
xor_pass = level.password.clone().unwrap_or(String::from("0"));
|
||||
}
|
||||
|
||||
let thing = [
|
||||
result.user_id.to_string(),
|
||||
result.stars.unwrap_or(0).to_string(),
|
||||
match difficulty {
|
||||
Some(diff) => {
|
||||
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
let compressed_level_data = fs::read(format!("{}/{}/{}.lvl", crate::CONFIG.db.data_folder, "levels", level.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 => 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
|
||||
}.to_string(),
|
||||
result.id.to_string(),
|
||||
result.rated_coins.to_string(),
|
||||
result.featured.to_string(),
|
||||
result.password.unwrap_or(String::new()).to_string(),
|
||||
0.to_string()
|
||||
];
|
||||
response.push(helpers::encryption::gen_solo_2(thing.join(",")));
|
||||
4 => String::from_utf8(level_data.to_vec()).expect("invalid utf-8 sequence"),
|
||||
5 => level.version.to_string(),
|
||||
6 => 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 => 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()))
|
||||
}
|
|
@ -2,53 +2,19 @@ use rocket::form::Form;
|
|||
use rocket::http::Status;
|
||||
use rocket::response::status;
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use diesel::prelude::*;
|
||||
|
||||
use sqlx::Type;
|
||||
use sqlx::{Encode, Sqlite, query_builder::QueryBuilder, Execute};
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
|
||||
use crate::helpers;
|
||||
use crate::db;
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
struct DynQuery {
|
||||
id: i64,
|
||||
name: String,
|
||||
user_id: i64,
|
||||
description: String,
|
||||
original: Option<i32>,
|
||||
game_version: i32,
|
||||
requested_stars: Option<i32>,
|
||||
version: i32,
|
||||
song_id: i32,
|
||||
length: i32,
|
||||
objects: i32,
|
||||
coins: i32,
|
||||
has_ldm: i32,
|
||||
two_player: i32,
|
||||
downloads: i32,
|
||||
likes: i32,
|
||||
difficulty: Option<i64>,
|
||||
community_difficulty: Option<i64>,
|
||||
demon_difficulty: Option<i64>,
|
||||
stars: Option<i32>,
|
||||
featured: i32,
|
||||
epic: i32,
|
||||
rated_coins: i32,
|
||||
user_username: String,
|
||||
user_udid: Option<String>,
|
||||
user_account_id: Option<i64>,
|
||||
user_registered: i32,
|
||||
editor_time: i32,
|
||||
editor_time_copies: i32
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct FormGetLevels {
|
||||
page: Option<i64>,
|
||||
str: Option<String>,
|
||||
page: i64,
|
||||
str: String,
|
||||
|
||||
accountID: Option<i64>,
|
||||
accountID: Option<i32>,
|
||||
gjp: Option<String>,
|
||||
gjp2: Option<String>,
|
||||
password: Option<String>,
|
||||
|
@ -75,57 +41,55 @@ pub struct FormGetLevels {
|
|||
}
|
||||
|
||||
#[post("/getGJLevels20.php", data = "<input>")]
|
||||
pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
|
||||
let mut connection = db::establish_sqlite_conn().await;
|
||||
pub fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static str> {
|
||||
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 query = levels::table.into_boxed();
|
||||
let mut count_query = levels::table.into_boxed();
|
||||
|
||||
// WHERE [...]
|
||||
let mut query_params: Vec<&str> = vec![];
|
||||
// Use this for binding on `query_params`
|
||||
let mut query_params_bind: Vec<Box<dyn ToString + Send>> = vec![];
|
||||
// ORDER BY [...]
|
||||
let mut order = "levels.created_at DESC";
|
||||
|
||||
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) => {
|
||||
if input.str != "" && input.r#type != Some(5) && input.r#type != Some(10) && input.r#type != Some(19) {
|
||||
match input.str.parse::<i32>() {
|
||||
Ok(matched_id) => {
|
||||
can_see_unlisted = true;
|
||||
query_params.push("levels.id = ?");
|
||||
query_params_bind.push(Box::new(id))
|
||||
query = query.filter(levels::id.eq(matched_id));
|
||||
count_query = count_query.filter(levels::id.eq(matched_id))
|
||||
},
|
||||
Err(_) => {
|
||||
query_params.push("levels.name LIKE ?");
|
||||
query_params_bind.push(Box::new(search_query.clone() + "%"));
|
||||
query = query.filter(levels::name.ilike(input.str.to_owned() + "%"));
|
||||
count_query = count_query.filter(levels::name.ilike(input.str.to_owned() + "%"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
match input.completedLevels.clone() {
|
||||
Some(completed_levels) => {
|
||||
let clean_levels: Vec<i64> = completed_levels[1..completed_levels.len() - 1].split(',')
|
||||
.map(|s| s.parse::<i64>().expect("failed to parse i64"))
|
||||
let clean_levels: Vec<i32> = completed_levels[1..completed_levels.len() - 1].split(',')
|
||||
.map(|s| s.parse::<i32>().expect("failed to parse i32"))
|
||||
.collect();
|
||||
let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
|
||||
query_params.push("levels.id NOT IN (?)");
|
||||
query_params_bind.push(Box::new(levels_str));
|
||||
query = query.filter(levels::id.ne_all(clean_levels.clone()));
|
||||
count_query = count_query.filter(levels::id.ne_all(clean_levels))
|
||||
},
|
||||
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 {
|
||||
match input.completedLevels.clone() {
|
||||
Some(completed_levels) => {
|
||||
let clean_levels: Vec<i64> = completed_levels[1..completed_levels.len() - 1].split(',')
|
||||
.map(|s| s.parse::<i64>().expect("failed to parse i64"))
|
||||
let clean_levels: Vec<i32> = completed_levels[1..completed_levels.len() - 1].split(',')
|
||||
.map(|s| s.parse::<i32>().expect("failed to parse i32"))
|
||||
.collect();
|
||||
let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
|
||||
query_params.push("levels.id IN (?)");
|
||||
query_params_bind.push(Box::new(levels_str));
|
||||
query = query.filter(levels::id.eq_any(clean_levels.clone()));
|
||||
count_query = count_query.filter(levels::id.eq_any(clean_levels))
|
||||
},
|
||||
None => return status::Custom(Status::Ok, "-1")
|
||||
}
|
||||
}
|
||||
if let Some(song_id) = input.song {
|
||||
if let Some(custom_song) = input.customSong {
|
||||
query_params.push("song_id = ?");
|
||||
query_params_bind.push(Box::new(custom_song));
|
||||
query = query.filter(levels::song_id.eq(custom_song));
|
||||
count_query = count_query.filter(levels::song_id.eq(custom_song))
|
||||
} else {
|
||||
query_params.push("song_id = ?");
|
||||
query_params_bind.push(Box::new(song_id));
|
||||
query = query.filter(levels::song_id.eq(song_id));
|
||||
count_query = count_query.filter(levels::song_id.eq(song_id));
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
unimplemented!("no gauntlet support")
|
||||
}
|
||||
if let Some(len) = input.len {
|
||||
query_params.push("levels.length = ?");
|
||||
query_params_bind.push(Box::new(len));
|
||||
query = query.filter(levels::length.eq(len));
|
||||
count_query = count_query.filter(levels::length.eq(len))
|
||||
}
|
||||
if let Some(diff) = input.diff.clone() {
|
||||
if diff != "-" {
|
||||
match diff.as_str() {
|
||||
"-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 {
|
||||
Some(demon_filter) => {
|
||||
match demon_filter {
|
||||
1 => {
|
||||
query_params.push("demon_difficulty = ?");
|
||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Easy.to_demon_difficulty()));
|
||||
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Easy.to_demon_difficulty()));
|
||||
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Easy.to_demon_difficulty()))
|
||||
},
|
||||
2 => {
|
||||
query_params.push("demon_difficulty = ?");
|
||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Medium.to_demon_difficulty()));
|
||||
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Medium.to_demon_difficulty()));
|
||||
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Medium.to_demon_difficulty()))
|
||||
},
|
||||
3 => {
|
||||
query_params.push("demon_difficulty = ?");
|
||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Hard.to_demon_difficulty()));
|
||||
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Hard.to_demon_difficulty()));
|
||||
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Hard.to_demon_difficulty()))
|
||||
},
|
||||
4 => {
|
||||
query_params.push("demon_difficulty = ?");
|
||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Insane.to_demon_difficulty()));
|
||||
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Insane.to_demon_difficulty()));
|
||||
count_query = count_query.filter(levels::demon_difficulty.eq::<i32>(crate::difficulty::DemonDifficulty::Insane.to_demon_difficulty()))
|
||||
},
|
||||
5 => {
|
||||
query_params.push("demon_difficulty = ?");
|
||||
query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Extreme.to_demon_difficulty()));
|
||||
query = query.filter(levels::demon_difficulty.eq::<i32>(crate::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!")
|
||||
}
|
||||
query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)");
|
||||
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()));
|
||||
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty()));
|
||||
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()))));
|
||||
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()))))
|
||||
},
|
||||
None => panic!("demon filter option with no demon filter argument")
|
||||
},
|
||||
"-3" => {
|
||||
query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)");
|
||||
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()));
|
||||
query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty()));
|
||||
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()))));
|
||||
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()))))
|
||||
},
|
||||
// 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"))
|
||||
.collect();
|
||||
let diffs_str = clean_diffs.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(", ");
|
||||
query_params.push("difficulty IN (?) OR (difficulty IS NULL AND community_difficulty IN (?))");
|
||||
query_params_bind.push(Box::new(diffs_str.clone()));
|
||||
query_params_bind.push(Box::new(diffs_str));
|
||||
query = query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs.clone()))));
|
||||
count_query = count_query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,11 +191,13 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
|||
match search_type {
|
||||
// downloads
|
||||
1 => {
|
||||
order = "levels.downloads DESC";
|
||||
query = query.order(levels::downloads.desc());
|
||||
// count query order doesnt matter
|
||||
},
|
||||
// likes
|
||||
2 => {
|
||||
order = "levels.likes DESC";
|
||||
query = query.order(levels::likes.desc());
|
||||
// count query order doesnt matter
|
||||
},
|
||||
// trending
|
||||
3 => {
|
||||
|
@ -247,9 +212,9 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
|||
5 => {
|
||||
if let Some(1) = input.local {
|
||||
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)) => {
|
||||
user_id_val = user_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")
|
||||
};
|
||||
|
||||
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;
|
||||
query_params.push("levels.user_id = ?");
|
||||
query_params_bind.push(Box::new(user_id_val));
|
||||
query = query.filter(levels::user_id.eq(user_id_val));
|
||||
count_query = count_query.filter(levels::user_id.eq(user_id_val))
|
||||
} else {
|
||||
return status::Custom(Status::Ok, "-1")
|
||||
}
|
||||
}
|
||||
}
|
||||
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_params_bind.push(Box::new(user_id_val));
|
||||
query = query.filter(levels::user_id.eq(user_id_val));
|
||||
count_query = count_query.filter(levels::user_id.eq(user_id_val))
|
||||
}
|
||||
}
|
||||
// featured
|
||||
// 17 is gdworld
|
||||
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
|
||||
16 => {
|
||||
query_params.push("epic = 1");
|
||||
query = query.filter(levels::epic.eq(1));
|
||||
count_query = count_query.filter(levels::epic.eq(1))
|
||||
},
|
||||
// magic
|
||||
7 => {
|
||||
query_params.push("objects > 4000");
|
||||
query = query.filter(levels::objects.gt(4000));
|
||||
count_query = count_query.filter(levels::objects.gt(4000))
|
||||
},
|
||||
// map packs 🙄😶
|
||||
10 | 19 => {
|
||||
|
@ -292,7 +260,8 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
|||
},
|
||||
// rated
|
||||
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
|
||||
12 => {
|
||||
|
@ -317,63 +286,72 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
|||
// default sort
|
||||
// 15 is gdworld
|
||||
0 | 15 | _ => {
|
||||
order = "likes DESC";
|
||||
query = query.order(levels::likes.desc());
|
||||
// count query order doesnt matter
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 query_base = format!("FROM levels JOIN users ON levels.user_id = users.id {} ORDER BY {}", where_str, order);
|
||||
let mut results: Vec<String> = [].to_vec();
|
||||
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 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);
|
||||
// }
|
||||
let mut hash_data: Vec<(i32, i32, bool)> = [].to_vec();
|
||||
|
||||
for result in {
|
||||
query
|
||||
.fetch_all(&mut connection)
|
||||
.await
|
||||
.expect("error loading levels")
|
||||
.order(levels::created_at.desc())
|
||||
.offset(input.page * 10)
|
||||
.limit(10)
|
||||
.get_results::<Level, >(connection)
|
||||
.expect("fatal error loading levels")
|
||||
} {
|
||||
let set_difficulty = result.difficulty.map(helpers::difficulty::LevelDifficulty::new);
|
||||
let community_difficulty = result.community_difficulty.map(helpers::difficulty::LevelDifficulty::new);
|
||||
let difficulty = set_difficulty.or(community_difficulty);
|
||||
let demon_difficulty = result.demon_difficulty.map(helpers::difficulty::DemonDifficulty::new);
|
||||
|
||||
let user: User = users::table.find(result.user_id).get_result::<User, >(connection).expect("couldnt get user from lvl");
|
||||
let level: Level = result;
|
||||
|
||||
let set_difficulty = match level.difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
let community_difficulty = match level.community_difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
let difficulty = match set_difficulty {
|
||||
Some(diff) => {
|
||||
Some(diff)
|
||||
},
|
||||
None => {
|
||||
match community_difficulty {
|
||||
Some(diff) => {
|
||||
Some(diff)
|
||||
},
|
||||
None => None
|
||||
}
|
||||
}
|
||||
};
|
||||
let demon_difficulty = match level.demon_difficulty {
|
||||
Some(diff) => {
|
||||
Some(helpers::difficulty::DemonDifficulty::new(diff))
|
||||
},
|
||||
None => None
|
||||
};
|
||||
|
||||
results.push(helpers::format::format(hashmap! {
|
||||
1 => result.id.to_string(),
|
||||
2 => result.name,
|
||||
3 => general_purpose::URL_SAFE.encode(result.description),
|
||||
5 => result.version.to_string(),
|
||||
6 => result.user_id.to_string(),
|
||||
1 => level.id.to_string(),
|
||||
2 => level.name,
|
||||
3 => general_purpose::URL_SAFE.encode(level.description),
|
||||
5 => level.version.to_string(),
|
||||
6 => user.id.to_string(),
|
||||
// this argument is weird. its the "difficulty divisor"
|
||||
// used to be vote count but yeah
|
||||
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(),
|
||||
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(),
|
||||
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 {
|
||||
|
@ -397,8 +375,8 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
|||
},
|
||||
None => 0
|
||||
}.to_string(),
|
||||
18 => (if let Some(stars) = result.stars { stars } else { 0 }).to_string(),
|
||||
19 => result.featured.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 {
|
||||
|
@ -409,49 +387,54 @@ pub async fn get_levels(input: Form<FormGetLevels>) -> status::Custom<&'static s
|
|||
},
|
||||
None => 0
|
||||
}.to_string(),
|
||||
30 => (if let Some(original) = result.original { original } else { 0 }).to_string(),
|
||||
31 => result.two_player.to_string(),
|
||||
35 => (if result.song_id >= 50 { result.song_id } else { 0 }).to_string(),
|
||||
37 => result.coins.to_string(),
|
||||
38 => result.rated_coins.to_string(),
|
||||
39 => (if let Some(requested_stars) = result.requested_stars { requested_stars } else { 0 }).to_string(),
|
||||
40 => result.has_ldm.to_string(),
|
||||
42 => result.epic.to_string(),
|
||||
30 => (if let Some(original) = level.original { original } else { 0 }).to_string(),
|
||||
31 => level.two_player.to_string(),
|
||||
35 => (if level.song_id >= 50 { level.song_id } else { 0 }).to_string(),
|
||||
37 => level.coins.to_string(),
|
||||
38 => level.rated_coins.to_string(),
|
||||
39 => (if let Some(requested_stars) = level.requested_stars { requested_stars } else { 0 }).to_string(),
|
||||
40 => level.has_ldm.to_string(),
|
||||
42 => level.epic.to_string(),
|
||||
43 => match demon_difficulty {
|
||||
Some(diff) => {
|
||||
diff
|
||||
},
|
||||
None => helpers::difficulty::DemonDifficulty::Hard
|
||||
}.to_demon_difficulty().to_string(),
|
||||
45 => result.objects.to_string(),
|
||||
46 => result.editor_time.to_string(),
|
||||
47 => result.editor_time_copies.to_string()
|
||||
45 => level.objects.to_string(),
|
||||
46 => level.editor_time.to_string(),
|
||||
47 => level.editor_time_copies.to_string()
|
||||
}));
|
||||
|
||||
users.push(format!("{}:{}:{}", result.user_id, result.user_username, {
|
||||
if result.user_registered == 1 {
|
||||
result.user_account_id.expect("wtf? registered user with no account id.").to_string()
|
||||
users.push(format!("{}:{}:{}", user.id, user.username, {
|
||||
if user.registered == 1 {
|
||||
user.account_id.expect("wtf? registered user with no account id.").to_string()
|
||||
} else {
|
||||
result.user_udid.expect("wtf? unregistered user with no udid.")
|
||||
user.udid.expect("wtf? unregistered user with no udid.")
|
||||
}
|
||||
}));
|
||||
|
||||
hash_data.push((
|
||||
result.id,
|
||||
{ if let Some(stars) = result.stars {
|
||||
level.id,
|
||||
{ if let Some(stars) = level.stars {
|
||||
stars
|
||||
} else {
|
||||
0
|
||||
}},
|
||||
{ if let 1 = result.rated_coins {
|
||||
{ if let 1 = level.rated_coins {
|
||||
true
|
||||
} else {
|
||||
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("#");
|
||||
|
||||
|
|
|
@ -2,17 +2,19 @@ use rocket::form::Form;
|
|||
use rocket::http::Status;
|
||||
use rocket::response::status;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
|
||||
use std::fs;
|
||||
|
||||
use crate::config;
|
||||
use crate::config::CONFIG;
|
||||
use crate::helpers;
|
||||
use crate::db;
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct FormUploadLevel {
|
||||
accountID: i64,
|
||||
accountID: i32,
|
||||
|
||||
gjp: Option<String>,
|
||||
gjp2: Option<String>,
|
||||
|
@ -22,7 +24,7 @@ pub struct FormUploadLevel {
|
|||
audioTrack: i32,
|
||||
levelName: String,
|
||||
levelDesc: String,
|
||||
levelID: i64,
|
||||
levelID: i32,
|
||||
levelVersion: i32,
|
||||
levelInfo: String,
|
||||
levelString: String,
|
||||
|
@ -38,14 +40,14 @@ pub struct FormUploadLevel {
|
|||
}
|
||||
|
||||
#[post("/uploadGJLevel21.php", data = "<input>")]
|
||||
pub async fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str> {
|
||||
let mut connection = db::establish_sqlite_conn().await;
|
||||
pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str> {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
// 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
|
||||
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)) => {
|
||||
user_id_val = user_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 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);
|
||||
|
||||
|
||||
// blocking coins
|
||||
if coins_val > 3 {
|
||||
return status::Custom(Status::Ok, "-1")
|
||||
}
|
||||
|
||||
|
||||
// too many objects
|
||||
let max_objects = config::config_get_with_default("levels.max_objects", 0) as usize;
|
||||
if max_objects != 0 && objects_val > max_objects {
|
||||
if objects_val > CONFIG.levels.max_objects as usize {
|
||||
return status::Custom(Status::Ok, "-1")
|
||||
}
|
||||
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
|
||||
// ACE vulnerability check
|
||||
for obj in level_objects.iter().filter(|obj| obj.item_block_id().is_some()) {
|
||||
if obj.item_block_id() < Some(0) || obj.item_block_id() > Some(1100) {
|
||||
return status::Custom(Status::Ok, "-1");
|
||||
}
|
||||
if let Some(_ace_object) = level_objects.iter().find(|obj| obj.item_block_id() < Some(0) || obj.item_block_id() > Some(1100)) {
|
||||
return status::Custom(Status::Ok, "-1")
|
||||
}
|
||||
|
||||
if sqlx::query_scalar!("SELECT COUNT(*) FROM levels WHERE id = ?", input.levelID)
|
||||
.fetch_one(&mut connection)
|
||||
.await
|
||||
.expect("error getting level count") > 0 {
|
||||
// update level
|
||||
// data base 🤣😁
|
||||
use crate::models::{Level, NewLevel};
|
||||
|
||||
let level_user_id = sqlx::query!("SELECT user_id FROM levels WHERE id = ?", input.levelID)
|
||||
.fetch_one(&mut connection)
|
||||
.await
|
||||
.expect("error getting level user id")
|
||||
.user_id;
|
||||
{
|
||||
use crate::schema::levels::dsl::*;
|
||||
|
||||
if level_user_id != user_id_val {
|
||||
return status::Custom(Status::Ok, "-1")
|
||||
if levels
|
||||
.filter(id.eq(input.levelID))
|
||||
.count()
|
||||
.get_result::<i64>(connection)
|
||||
.expect("couldnt get count of levels") > 0 {
|
||||
// update level
|
||||
let level_user_id = levels
|
||||
.filter(id.eq(input.levelID))
|
||||
.select(user_id)
|
||||
.get_result::<i32>(connection)
|
||||
.expect("couldnt query levels");
|
||||
|
||||
if level_user_id != user_id_val {
|
||||
return status::Custom(Status::Ok, "-1")
|
||||
}
|
||||
|
||||
let updated_level = diesel::update(levels)
|
||||
.filter(id.eq(input.levelID))
|
||||
.set((
|
||||
description.eq(description_val.chars().take(140).collect::<String>()),
|
||||
password.eq(input.password.clone()),
|
||||
requested_stars.eq(match input.requestedStars {
|
||||
Some(requested_stars_val) => requested_stars_val.clamp(0, 10),
|
||||
None => 0
|
||||
}),
|
||||
version.eq(input.levelVersion),
|
||||
extra_data.eq(extra_string.as_bytes().to_owned()),
|
||||
level_info.eq(input.levelInfo.clone().into_bytes()),
|
||||
editor_time.eq(input.wt.unwrap_or(0)),
|
||||
editor_time_copies.eq(input.wt2.unwrap_or(0)),
|
||||
song_id.eq(song_id_val),
|
||||
length.eq(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::response::status;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::helpers;
|
||||
use crate::db;
|
||||
|
||||
|
@ -12,21 +14,31 @@ pub struct FormGetUsers {
|
|||
}
|
||||
|
||||
#[post("/getGJUsers20.php", data = "<input>")]
|
||||
pub async fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str> {
|
||||
let mut connection = db::establish_sqlite_conn().await;
|
||||
pub fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str> {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let username = input.str.to_owned() + "%";
|
||||
let offset = input.page * 10;
|
||||
// query users
|
||||
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)
|
||||
.fetch_all(&mut connection)
|
||||
.await
|
||||
.expect("Fatal error loading users");
|
||||
let mut query_users = users.into_boxed();
|
||||
|
||||
match input.str.parse::<i32>() {
|
||||
Ok(id_value) => query_users = query_users.filter(id.eq(id_value)),
|
||||
Err(_) => query_users = query_users.filter(username.ilike(input.str.to_owned() + "%"))
|
||||
};
|
||||
|
||||
let mut results: Vec<String> = vec![];
|
||||
|
||||
for result in query_results {
|
||||
let user = result;
|
||||
for result in {
|
||||
query_users
|
||||
.order(stars.desc())
|
||||
.offset(input.page * 10)
|
||||
.limit(10)
|
||||
.get_results::<User, >(connection)
|
||||
.expect("Fatal error loading users")
|
||||
} {
|
||||
let user: User = result;
|
||||
|
||||
let formatted_result = helpers::format::format(hashmap! {
|
||||
1 => user.username,
|
||||
|
@ -63,15 +75,22 @@ pub async fn get_users(input: Form<FormGetUsers>) -> status::Custom<&'static str
|
|||
results.push(formatted_result)
|
||||
};
|
||||
|
||||
let amount = sqlx::query_scalar!("SELECT COUNT(*) FROM users WHERE id = ? OR username LIKE ?", input.str, username)
|
||||
.fetch_one(&mut connection)
|
||||
.await
|
||||
.expect("error loading users");
|
||||
let mut query_users_count = users.into_boxed();
|
||||
|
||||
match input.str.parse::<i32>() {
|
||||
Ok(id_value) => query_users_count = query_users_count.filter(id.eq(id_value)),
|
||||
Err(_) => query_users_count = query_users_count.filter(username.ilike(input.str.to_owned() + "%"))
|
||||
};
|
||||
|
||||
let amount = query_users_count
|
||||
.count()
|
||||
.get_result::<i64>(connection)
|
||||
.expect("error querying user count");
|
||||
|
||||
let response = if results.is_empty() {
|
||||
String::from("-1")
|
||||
} 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()))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use diesel::prelude::*;
|
||||
use password_auth::verify_password;
|
||||
|
||||
use crate::db;
|
||||
use crate::helpers;
|
||||
use crate::{db, helpers};
|
||||
|
||||
// returns userid, accountid
|
||||
pub enum AuthError {
|
||||
|
@ -9,36 +9,39 @@ pub enum AuthError {
|
|||
AccountNotFound
|
||||
}
|
||||
|
||||
pub async fn auth(account_id: i64, password_val: Option<String>, gjp_val: Option<String>, gjp2_val: Option<String>) -> Result<(i64, i64), AuthError> {
|
||||
let connection = &mut db::establish_sqlite_conn().await;
|
||||
pub fn auth(account_id: i32, password_val: Option<String>, gjp_val: Option<String>, gjp2_val: Option<String>) -> Result<(i32, i32), AuthError> {
|
||||
use crate::schema::accounts::dsl::*;
|
||||
|
||||
let query_result = sqlx::query!("SELECT password, gjp2 FROM accounts WHERE id = ?", account_id)
|
||||
.fetch_one(connection)
|
||||
.await;
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let query_result = accounts
|
||||
.select((password, gjp2))
|
||||
.filter(id.eq(account_id))
|
||||
.get_result::<(String, String)>(connection);
|
||||
|
||||
match query_result {
|
||||
Ok(result) => {
|
||||
let password_queried_val = result.password;
|
||||
let gjp2_queried_val = result.gjp2;
|
||||
|
||||
Ok((
|
||||
password_queried_val,
|
||||
gjp2_queried_val
|
||||
)) => {
|
||||
match password_val {
|
||||
Some(password_val) => {
|
||||
match verify_password(password_val, &password_queried_val) {
|
||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
|
||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
|
||||
Err(_) => return Err(AuthError::WrongPassword)
|
||||
}
|
||||
},
|
||||
None => match gjp_val {
|
||||
Some(gjp_val) => {
|
||||
match verify_password(helpers::encryption::decode_gjp(gjp_val), &password_queried_val) {
|
||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
|
||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
|
||||
Err(_) => return Err(AuthError::WrongPassword)
|
||||
}
|
||||
},
|
||||
None => match gjp2_val {
|
||||
Some(gjp2_val) => {
|
||||
match verify_password(gjp2_val, &gjp2_queried_val) {
|
||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id).await, account_id)),
|
||||
Ok(_) => return Ok((get_user_id_from_account_id(account_id), account_id)),
|
||||
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 {
|
||||
let connection = &mut db::establish_sqlite_conn().await;
|
||||
pub fn get_user_id_from_account_id(ext_id: i32) -> i32 {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
let user_id = sqlx::query!("SELECT id FROM users WHERE account_id = ?", ext_id)
|
||||
.fetch_one(connection)
|
||||
.await
|
||||
.expect("no user associated with account id??")
|
||||
.id;
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let user_id = users
|
||||
.filter(udid.eq(ext_id.to_string()).or(account_id.eq(ext_id)))
|
||||
.select(id)
|
||||
.get_result::<i32>(connection)
|
||||
.expect("No user associated with account?!?!?");
|
||||
|
||||
return user_id
|
||||
}
|
|
@ -8,9 +8,4 @@ pub fn clean_no_space(string: &str) -> String {
|
|||
pub fn clean_basic(string: &str) -> String {
|
||||
let regex = Regex::new(r"[^A-Za-z0-9\-_ ]").unwrap();
|
||||
return regex.replace_all(string, "").to_string();
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn new(value: i64) -> LevelDifficulty {
|
||||
pub fn new(value: i32) -> LevelDifficulty {
|
||||
match value {
|
||||
0 => LevelDifficulty::Auto,
|
||||
1 => LevelDifficulty::Easy,
|
||||
|
@ -23,7 +23,7 @@ impl LevelDifficulty {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn value(self) -> i64 {
|
||||
pub fn value(self) -> i32 {
|
||||
match self {
|
||||
LevelDifficulty::Auto => 0,
|
||||
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 {
|
||||
LevelDifficulty::Auto => 5,
|
||||
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 {
|
||||
1 => LevelDifficulty::Auto,
|
||||
2 => LevelDifficulty::Easy,
|
||||
|
@ -70,7 +70,7 @@ pub enum DemonDifficulty {
|
|||
}
|
||||
|
||||
impl DemonDifficulty {
|
||||
pub fn new(value: i64) -> DemonDifficulty {
|
||||
pub fn new(value: i32) -> DemonDifficulty {
|
||||
match value {
|
||||
0 => DemonDifficulty::Easy,
|
||||
1 => DemonDifficulty::Medium,
|
||||
|
@ -81,7 +81,7 @@ impl DemonDifficulty {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn value(self) -> i64 {
|
||||
pub fn value(self) -> i32 {
|
||||
match self {
|
||||
DemonDifficulty::Easy => 0,
|
||||
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 {
|
||||
DemonDifficulty::Easy => 3,
|
||||
DemonDifficulty::Medium => 4,
|
||||
|
|
|
@ -29,7 +29,7 @@ pub fn decode_gjp(gjp: String) -> String {
|
|||
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();
|
||||
|
||||
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 uncompressed_data = String::from_utf8(if decoded_bytes.starts_with(&[0x1F, 0x8B]) {
|
||||
// gzip!!
|
||||
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");
|
||||
let mut uncompressed_data = String::new();
|
||||
decoder.read_to_string(&mut uncompressed_data).expect("err unzipping level");
|
||||
|
||||
return parse(uncompressed_data.as_str())
|
||||
}
|
||||
|
|
|
@ -1,51 +1,59 @@
|
|||
use std::sync::RwLock;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::db;
|
||||
|
||||
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() {
|
||||
let mut connection = db::establish_sqlite_conn().await;
|
||||
pub fn init() {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let result = sqlx::query!("SELECT id FROM accounts WHERE username = ?", REUPLOAD_USER_NAME)
|
||||
.fetch_one(&mut connection)
|
||||
.await;
|
||||
use crate::schema::{accounts, users};
|
||||
use crate::models::{Account, NewAccount, User, NewUser};
|
||||
|
||||
match result {
|
||||
Ok(result) => {
|
||||
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock");
|
||||
*write_lock = result.id;
|
||||
},
|
||||
Err(_) => {
|
||||
let new_account = sqlx::query!(
|
||||
"INSERT INTO accounts (username, gjp2, password, email) VALUES (?, ?, ?, ?)",
|
||||
REUPLOAD_USER_NAME,
|
||||
"!",
|
||||
"!",
|
||||
""
|
||||
)
|
||||
.execute(&mut connection)
|
||||
.await
|
||||
.expect("error saving the new account");
|
||||
match accounts::table
|
||||
.filter(accounts::username.eq(REUPLOAD_USER_NAME))
|
||||
.select(accounts::id)
|
||||
.get_result::<i32, >(connection) {
|
||||
Ok(reupload_acc_id) => {
|
||||
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock!!");
|
||||
*write_lock = reupload_acc_id;
|
||||
|
||||
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!(
|
||||
"INSERT INTO users (account_id, username, registered) VALUES (?, ?, ?)",
|
||||
reupload_acc_id,
|
||||
REUPLOAD_USER_NAME,
|
||||
1
|
||||
)
|
||||
.execute(&mut connection)
|
||||
.await
|
||||
.expect("error saving the new user");
|
||||
let inserted_account = diesel::insert_into(accounts::table)
|
||||
.values(&new_account)
|
||||
.get_result::<Account, >(connection)
|
||||
.expect("Fatal error saving the new account");
|
||||
|
||||
let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock");
|
||||
*write_lock = reupload_acc_id;
|
||||
let reupload_acc_id = inserted_account.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;
|
||||
|
||||
mod config;
|
||||
mod db;
|
||||
mod endpoints;
|
||||
use db::*;
|
||||
|
||||
mod helpers;
|
||||
use helpers::*;
|
||||
|
||||
mod endpoints;
|
||||
use endpoints::*;
|
||||
|
||||
mod template_endpoints;
|
||||
use template_endpoints::*;
|
||||
|
||||
mod config;
|
||||
use config::*;
|
||||
|
||||
#[get("/<file..>")]
|
||||
async fn files(file: PathBuf) -> Option<NamedFile> {
|
||||
|
@ -24,19 +33,20 @@ async fn files(file: PathBuf) -> Option<NamedFile> {
|
|||
}
|
||||
|
||||
#[launch]
|
||||
async fn rocket() -> _ {
|
||||
fn rocket() -> _ {
|
||||
// init stuff
|
||||
crate::helpers::reupload::init().await;
|
||||
crate::helpers::reupload::init();
|
||||
|
||||
// data directories
|
||||
// unhardcore this maybe?
|
||||
fs::create_dir_all(config::config_get_with_default("db.data_folder", "data".to_string())).expect("failed to create data directory!");
|
||||
fs::create_dir_all(format!("{}/levels", config::config_get_with_default("db.data_folder", "data".to_string()))).expect("failed to create data directory for levels");
|
||||
// this is a bit scuffed
|
||||
fs::create_dir_all(&CONFIG.db.data_folder).expect("failed to create data directory!");
|
||||
fs::create_dir_all(format!("{}/levels", &CONFIG.db.data_folder)).expect("failed to create data directory for levels");
|
||||
|
||||
rocket::build()
|
||||
// conf
|
||||
.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()))))
|
||||
// actual website
|
||||
.mount("/", routes![
|
||||
|
@ -57,9 +67,10 @@ async fn rocket() -> _ {
|
|||
files
|
||||
])
|
||||
// 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::register_account::register_account,
|
||||
endpoints::accounts::update_account_settings::update_account_settings,
|
||||
|
||||
endpoints::users::get_users::get_users,
|
||||
|
||||
|
@ -67,6 +78,6 @@ async fn rocket() -> _ {
|
|||
endpoints::levels::get_levels::get_levels,
|
||||
endpoints::levels::upload_level::upload_level
|
||||
])
|
||||
// so templates work
|
||||
// so templates work i think
|
||||
.attach(Template::fairing())
|
||||
}
|
|
@ -4,20 +4,25 @@ use rocket_dyn_templates::{Template, context};
|
|||
|
||||
use rocket::http::CookieJar;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::db;
|
||||
|
||||
#[get("/accounts")]
|
||||
pub async fn account_management(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
|
||||
let connection = &mut db::establish_sqlite_conn().await;
|
||||
#[get("/accounts")]
|
||||
pub fn account_management(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let logged_in = crate::helpers::templates::authenticate(cookies);
|
||||
|
||||
match logged_in {
|
||||
Ok((username_val, _account_id, user_id)) => {
|
||||
let result = sqlx::query!("SELECT stars, demons, coins, user_coins, diamonds, creator_points FROM users WHERE id = ?", user_id)
|
||||
.fetch_one(connection)
|
||||
.await
|
||||
.expect("couldnt query database");
|
||||
Ok((username_val, _account_id_val, user_id_val)) => {
|
||||
use crate::schema::users::dsl::*;
|
||||
use crate::models::User;
|
||||
|
||||
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! {
|
||||
username: username_val,
|
||||
|
|
|
@ -8,6 +8,8 @@ use rocket::http::{Cookie, CookieJar};
|
|||
|
||||
use rocket::time::Duration;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::db;
|
||||
use crate::helpers;
|
||||
|
||||
|
@ -18,23 +20,25 @@ pub struct FormLogin {
|
|||
}
|
||||
|
||||
#[post("/login", data = "<input>")]
|
||||
pub async fn post_login(cookies: &CookieJar<'_>, input: Form<FormLogin>) -> Template {
|
||||
let connection = &mut db::establish_sqlite_conn().await;
|
||||
pub fn post_login(cookies: &CookieJar<'_>, input: Form<FormLogin>) -> Template {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let result = sqlx::query!("SELECT id, username FROM accounts WHERE username = ?", input.username)
|
||||
.fetch_one(connection)
|
||||
.await;
|
||||
use crate::schema::accounts::dsl::*;
|
||||
|
||||
let result = accounts
|
||||
.select((id, username))
|
||||
.filter(username.eq(input.username.clone()))
|
||||
.get_result::<(i32, String), >(connection);
|
||||
|
||||
match result {
|
||||
Ok(result) => {
|
||||
let account_username = result.username;
|
||||
|
||||
match helpers::accounts::auth(result.id, Some(input.password.clone()), None, None).await {
|
||||
Ok(account_id_username_val) => {
|
||||
match helpers::accounts::auth(account_id_username_val.0, Some(input.password.clone()), None, None) {
|
||||
Ok(account_id_user_id_val) => {
|
||||
cookies.add_private(Cookie::build(
|
||||
"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("/")
|
||||
// should probably make this true when we get into production
|
||||
.secure(false)
|
||||
.http_only(true)
|
||||
.max_age(Duration::days(365))
|
||||
|
|
|
@ -12,9 +12,10 @@ use std::fs;
|
|||
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::helpers;
|
||||
use crate::db;
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LevelResults {
|
||||
|
@ -36,9 +37,9 @@ pub struct FormReupload {
|
|||
|
||||
#[post("/tools/reupload", data = "<input>")]
|
||||
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 {
|
||||
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 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 reupload_account_id = helpers::reupload::REUPLOAD_ACCOUNT_ID.read().expect("poisoned lock!!").to_string().parse::<i32>().expect("reupload account id not int (shouldnt ever happen)");
|
||||
let level_description = String::from_utf8(general_purpose::URL_SAFE.decode(general_purpose::URL_SAFE.decode(level_data.get("k3").expect("level description not found")).expect("couldnt decode base64")).expect("couldnt decode base64")).expect("invalid utf-8 sequence (how)");
|
||||
let level_game_version = level_data.get("k17").expect("level game version not found").to_string().parse::<i32>().expect("level game version not int");
|
||||
let level_binary_version = level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::<i32>().expect("level binary version not int");
|
||||
let level_password = level_data.get("k41").expect("level password not found").to_string();
|
||||
let level_requested_stars = level_data.get("k66").expect("level requested stars not found").to_string().parse::<i32>().expect("level requested stars not int");
|
||||
let level_version = level_data.get("k16").expect("level version not found").to_string().parse::<i32>().expect("level version not int");
|
||||
let extra_string = level_data.get("extra_string").unwrap_or(&crate::helpers::levels::DEFAULT_EXTRA_STRING).to_string().into_bytes();
|
||||
let default_level_info = crate::helpers::levels::DEFAULT_LEVEL_INFO.to_string().into_bytes();
|
||||
let level_editor_time = level_data.get("k80").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time not int");
|
||||
let level_editor_time_copies = level_data.get("k81").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time copies not int");
|
||||
let level_song_id = if level_data.get("k8").unwrap_or(&String::from("0")).parse::<i32>().expect("level song id not int") == 0 {
|
||||
level_data.get("k45").expect("level song id doesnt fucking exist").parse::<i32>().expect("level song id not int")
|
||||
} else {
|
||||
level_data.get("k8").expect("level song id doesnt fucking exist").parse::<i32>().expect("level song id not int")
|
||||
let new_level = NewLevel {
|
||||
name: level_data.get("k2").expect("level name not found").to_string(),
|
||||
user_id: crate::helpers::reupload::REUPLOAD_ACCOUNT_ID.read().expect("poisoned lock!!").to_string().parse::<i32>().expect("reupload account id not int (shouldnt ever happen)"),
|
||||
description: String::from_utf8(general_purpose::URL_SAFE.decode(general_purpose::URL_SAFE.decode(level_data.get("k3").expect("level description not found")).expect("couldnt decode base64")).expect("couldnt decode base64")).expect("invalid utf-8 sequence (how)"),
|
||||
original: None,
|
||||
game_version: level_data.get("k17").expect("level game version not found").to_string().parse::<i32>().expect("level game version not int"),
|
||||
binary_version: level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::<i32>().expect("level binary version not int"),
|
||||
password: Some(level_data.get("k41").expect("level password not found").to_string()),
|
||||
requested_stars: level_data.get("k66").expect("level requested stars not found").to_string().parse::<i32>().expect("level requested stars not int"),
|
||||
unlisted: 0,
|
||||
version: level_data.get("k16").expect("level version not found").to_string().parse::<i32>().expect("level version not int"),
|
||||
extra_data: level_data.get("extra_string").unwrap_or(&crate::helpers::levels::DEFAULT_EXTRA_STRING).to_string().into_bytes(),
|
||||
level_info: crate::helpers::levels::DEFAULT_LEVEL_INFO.to_string().into_bytes(),
|
||||
editor_time: level_data.get("k80").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time not int"),
|
||||
editor_time_copies: level_data.get("k81").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time copies not int"),
|
||||
song_id: if level_data.get("k8").unwrap_or(&String::from("0")).parse::<i32>().expect("level song id not int") == 0 {
|
||||
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!(
|
||||
"INSERT INTO levels (name, user_id, description, game_version, binary_version, password, requested_stars, unlisted, version, extra_data, level_info, editor_time, editor_time_copies, song_id, length, objects, coins, has_ldm, two_player)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
level_name,
|
||||
reupload_account_id,
|
||||
level_description,
|
||||
level_game_version,
|
||||
level_binary_version,
|
||||
level_password,
|
||||
level_requested_stars,
|
||||
0,
|
||||
level_version,
|
||||
extra_string,
|
||||
default_level_info,
|
||||
level_editor_time,
|
||||
level_editor_time_copies,
|
||||
level_song_id,
|
||||
level_length,
|
||||
level_object_count,
|
||||
level_coins,
|
||||
level_ldm,
|
||||
level_two_player
|
||||
)
|
||||
.execute(connection)
|
||||
.await
|
||||
.expect("couldnt write to db");
|
||||
let inserted_level = diesel::insert_into(levels)
|
||||
.values(&new_level)
|
||||
.get_result::<Level, >(connection)
|
||||
.expect("failed to insert level");
|
||||
|
||||
// sqlite doesnt have return clause :frown: maybe swap to custom id system
|
||||
let inserted_id = inserted_level.last_insert_rowid();
|
||||
|
||||
fs::write(format!("{}/levels/{}.lvl", config::config_get_with_default("db.data_folder", "data".to_string()), inserted_id), general_purpose::URL_SAFE.decode(level_data.get("k4").expect("no level data?!").as_bytes()).expect("user provided invalid level string")).expect("couldnt write level to file");
|
||||
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");
|
||||
|
||||
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")]
|
||||
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! {
|
||||
disabled: disabled
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue