From 5df6063ff1e0bae426ff56c8ec6f324d74bebe66 Mon Sep 17 00:00:00 2001 From: niko Date: Mon, 11 Dec 2023 20:02:10 -0700 Subject: [PATCH] wip: no errors --- .cargo/config.toml | 2 + Cargo.lock | 17 + Cargo.toml | 2 +- build.rs | 2 +- src/auth.rs | 91 ++-- src/cdn.rs | 73 ++-- src/main.rs | 72 ++-- src/{entities.rs => models.rs} | 64 +-- src/mods.rs | 757 +++++++++++++++++++++------------ src/schema.rs | 81 ++-- src/users.rs | 163 ++++--- src/versions.rs | 72 ++-- 12 files changed, 879 insertions(+), 517 deletions(-) create mode 100644 .cargo/config.toml rename src/{entities.rs => models.rs} (68%) diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..678266e --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["-C", "link-arg=-fuse-ld=lld"] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0873b2b..6a1f687 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1159,6 +1159,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "hex" version = "0.4.3" @@ -1723,6 +1729,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.32.1" @@ -3114,6 +3130,7 @@ dependencies = [ "bytes", "libc", "mio", + "num_cpus", "parking_lot", "pin-project-lite", "socket2 0.5.5", diff --git a/Cargo.toml b/Cargo.toml index 7bd0df8..c1b2b9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,6 @@ tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.18", features = ["chrono", "env-filter", "fmt"] } poem = "1.3.59" async-graphql-poem = "6.0.11" -tokio = { version = "1.35.0", features = ["macros"] } +tokio = { version = "1.35.0", features = ["macros", "rt-multi-thread"] } sqlx = { version = "0.7.3", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono"] } anyhow = "1.0.75" diff --git a/build.rs b/build.rs index 7609593..d506869 100644 --- a/build.rs +++ b/build.rs @@ -2,4 +2,4 @@ fn main() { // trigger recompilation when a new migration is added println!("cargo:rerun-if-changed=migrations"); -} \ No newline at end of file +} diff --git a/src/auth.rs b/src/auth.rs index 63acaa0..bc10d38 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,9 +1,9 @@ use chrono::{DateTime, Utc}; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, DatabaseConnection}; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use sqlx::PgPool; use uuid::Uuid; -use crate::{KEY, Key, users::User}; +use crate::{models, users::User, Key, KEY}; bitflags::bitflags! { pub struct Permission: i32 { @@ -23,7 +23,7 @@ bitflags::bitflags! { #[derive(Debug, Serialize, Deserialize)] pub struct JWTAuth { - pub user: entity::users::Model, + pub user: models::dUser, #[serde(with = "chrono::serde::ts_seconds")] pub(crate) exp: DateTime, // Required (validate_exp defaults to true in validation). Expiration time (as UTC timestamp) #[serde(with = "chrono::serde::ts_seconds")] @@ -31,7 +31,7 @@ pub struct JWTAuth { } impl JWTAuth { - pub fn new(user: entity::users::Model) -> Self { + pub fn new(user: models::dUser) -> Self { let now = Utc::now(); Self { @@ -47,20 +47,26 @@ impl JWTAuth { pub fn decode(dec: String, key: Key) -> Option { let token = match jsonwebtoken::decode::( - &dec, - &jsonwebtoken::DecodingKey::from_secret(&key.0), - &jsonwebtoken::Validation::default(), - ) { - Err(_) => { return None; }, - Ok(t) => if t.claims.is_valid() { Some(t) } else { None } + &dec, + &jsonwebtoken::DecodingKey::from_secret(&key.0), + &jsonwebtoken::Validation::default(), + ) { + Err(_) => { + return None; + } + Ok(t) => { + if t.claims.is_valid() { + Some(t) + } else { + None + } + } }; Some(token.unwrap().claims) } pub fn encode(&self, key: Key) -> String { - - jsonwebtoken::encode( &jsonwebtoken::Header::default(), &self, @@ -74,50 +80,53 @@ impl JWTAuth { pub enum Authorization { Session(String), ApiKey(Uuid), - None + None, } impl Authorization { pub fn parse(s: Option) -> Self { match s { - Some(s) => { - match Uuid::parse_str(&s) { - Ok(uuid) => Self::ApiKey(uuid), - Err(_) => Self::Session(s) - } + Some(s) => match Uuid::parse_str(&s) { + Ok(uuid) => Self::ApiKey(uuid), + Err(_) => Self::Session(s), }, - None => Self::None + None => Self::None, } } - pub async fn get_user(&self, db: &DatabaseConnection) -> Option { + pub async fn get_user(&self, db: &PgPool) -> Option { match self { Self::Session(s) => { let auth = JWTAuth::decode(s.to_string(), *KEY.clone()); match auth { Some(auth) => { - let user = entity::users::Entity::find_by_id(auth.user.id) - .one(db) - .await - .unwrap() - .unwrap(); + let user = sqlx::query_as!( + models::dUser, + "SELECT * FROM users WHERE id = $1", + auth.user.id + ) + .fetch_one(db) + .await + .unwrap(); Some(user) - }, - None => None + } + None => None, } - }, + } Self::ApiKey(uuid) => { - let user = entity::users::Entity::find() - .filter(entity::users::Column::ApiKey.eq(sea_orm::prelude::Uuid::from_bytes(*uuid.as_bytes()))) - .one(db) - .await - .unwrap() - .unwrap(); + let user = sqlx::query_as!( + models::dUser, + "SELECT * FROM users WHERE api_key = $1", + sqlx::types::Uuid::from_bytes(*uuid.as_bytes()) + ) + .fetch_one(db) + .await + .unwrap(); Some(user) - }, - _ => None + } + _ => None, } } } @@ -132,7 +141,7 @@ impl HasPermissions for &User { } } -impl HasPermissions for &entity::users::Model { +impl HasPermissions for &models::dUser { fn permissions(&self) -> i32 { self.permissions } @@ -144,7 +153,7 @@ impl HasPermissions for &mut User { } } -impl HasPermissions for &mut entity::users::Model { +impl HasPermissions for &mut models::dUser { fn permissions(&self) -> i32 { self.permissions } @@ -156,7 +165,7 @@ impl HasPermissions for User { } } -impl HasPermissions for entity::users::Model { +impl HasPermissions for models::dUser { fn permissions(&self) -> i32 { self.permissions } @@ -164,4 +173,4 @@ impl HasPermissions for entity::users::Model { pub async fn validate_permissions(user: T, required: Permission) -> bool { required.bits() & user.permissions() != 0 -} \ No newline at end of file +} diff --git a/src/cdn.rs b/src/cdn.rs index 18b81e4..131c88a 100644 --- a/src/cdn.rs +++ b/src/cdn.rs @@ -1,11 +1,10 @@ -use entity::prelude::*; use forge_lib::structs::v1::{unpack_v1_forgemod, ForgeModTypes}; -use poem::{handler, web::Path, Response, IntoResponse, http::StatusCode}; -use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, DatabaseConnection}; +use poem::{handler, http::StatusCode, web::Path, IntoResponse, Response}; use serde::Deserialize; +use sqlx::PgPool; -use crate::DB_POOL; +use crate::{models, DB_POOL}; #[derive(Copy, Clone, Debug, Deserialize)] #[serde(rename_all = "lowercase")] @@ -15,24 +14,37 @@ pub enum CdnType { } async fn cdn_handler( - db: DatabaseConnection, + db: &PgPool, slug: String, version: String, dl_type: CdnType, ) -> impl IntoResponse { - let db_mod = Mods::find() - .filter(entity::mods::Column::Slug.eq(&slug)) - .one(&db) + // let db_mod = Mods::find() + // .filter(entity::mods::Column::Slug.eq(&slug)) + // .one(&db) + // .await + // .unwrap(); + let db_mod = sqlx::query_as!(models::dMod, "SELECT * FROM mods WHERE slug = $1", slug) + .fetch_optional(db) .await .unwrap(); if let Some(db_mod) = db_mod { - let db_version = Versions::find() - .filter(entity::versions::Column::ModId.eq(db_mod.id)) - .filter(entity::versions::Column::Version.eq(&version)) - .one(&db) - .await - .unwrap(); + // let db_version = Versions::find() + // .filter(entity::versions::Column::ModId.eq(db_mod.id)) + // .filter(entity::versions::Column::Version.eq(&version)) + // .one(&db) + // .await + // .unwrap(); + let db_version = sqlx::query_as!( + models::dVersion, + "SELECT * FROM versions WHERE mod_id = $1 AND version = $2", + db_mod.id, + version + ) + .fetch_optional(db) + .await + .unwrap(); if let Some(db_version) = db_version { let file = match std::fs::read(format!( @@ -40,36 +52,49 @@ async fn cdn_handler( db_mod.id, db_version.id )) { Ok(file) => file, - Err(_) => return Response::builder().status(StatusCode::NOT_FOUND).body("Not Found"), + Err(_) => { + return Response::builder() + .status(StatusCode::NOT_FOUND) + .body("Not Found") + } }; match dl_type { CdnType::Dll => { let package = unpack_v1_forgemod(&*file).unwrap(); match package { - ForgeModTypes::Mod(m) => - { + ForgeModTypes::Mod(m) => { return Response::builder() .header("Content-Type", "application/octet-stream") - .header("Content-Disposition", format!("attachment; filename=\"{}.dll\"", m.manifest._id)) + .header( + "Content-Disposition", + format!("attachment; filename=\"{}.dll\"", m.manifest._id), + ) .body(m.data.artifact_data); - }, + } _ => { - return Response::builder().status(StatusCode::NOT_FOUND).body("Not Found"); + return Response::builder() + .status(StatusCode::NOT_FOUND) + .body("Not Found"); } } } CdnType::Package => { return Response::builder() .header("Content-Type", "application/octet-stream") - .header("Content-Disposition", format!("attachment; filename=\"{}-v{}.beatforge\"", slug, version)) + .header( + "Content-Disposition", + format!("attachment; filename=\"{}-v{}.beatforge\"", slug, version), + ) .body(file); } } } } - Response::builder().status(StatusCode::NOT_FOUND).body("Not Found") + Response::builder() + .status(StatusCode::NOT_FOUND) + .body("Not Found") } // #[get("/cdn/{slug}@{version}/{type}")] @@ -79,7 +104,7 @@ pub async fn cdn_get( // path: web::Path<(String, String, CdnType)>, Path((slug, version, dl_type)): Path<(String, String, CdnType)>, ) -> impl IntoResponse { - let db = DB_POOL.get().unwrap().clone(); + let db = DB_POOL.get().unwrap(); cdn_handler(db, slug, version, dl_type).await } @@ -91,7 +116,7 @@ pub async fn cdn_get_typeless( // path: web::Path<(String, String)>, Path((slug, version)): Path<(String, String)>, ) -> impl IntoResponse { - let db = DB_POOL.get().unwrap().clone(); + let db = DB_POOL.get().unwrap(); cdn_handler(db, slug, version, CdnType::Package).await } diff --git a/src/main.rs b/src/main.rs index 29a3d48..d9f3e63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{io, path::Path, sync::Arc}; +use std::{path::Path, sync::Arc}; use async_graphql::{ http::{playground_source, GraphQLPlaygroundConfig, GraphiQLSource}, @@ -7,22 +7,19 @@ use async_graphql::{ use async_graphql_poem::GraphQL; use cached::async_sync::OnceCell; use meilisearch_sdk::settings::Settings; -use poem::{ - get, handler, listener::TcpListener, post, IntoResponse, Response, - Route, -}; +use poem::{get, handler, listener::TcpListener, post, IntoResponse, Response, Route}; use rand::Rng; -use sea_orm::{DatabaseConnection, EntityTrait, PaginatorTrait}; -use sqlx::{PgPool, postgres::PgPoolOptions, migrate::Migrator}; +use sqlx::{migrate::Migrator, postgres::PgPoolOptions, PgPool}; use tracing::log::info; mod auth; mod cdn; +mod models; mod mods; mod schema; mod users; mod versions; -mod entities; +mod search; use crate::schema::Query; @@ -89,8 +86,22 @@ pub static MIGRATOR: Migrator = sqlx::migrate!(); async fn index() -> impl IntoResponse { let db = DB_POOL.get().unwrap().clone(); - let user_count = entity::users::Entity::find().count(&db).await.unwrap(); - let mod_count = entity::mods::Entity::find().count(&db).await.unwrap(); + // let user_count = entity::users::Entity::find().count(&db).await.unwrap(); + // let mod_count = entity::mods::Entity::find().count(&db).await.unwrap(); + + let user_count = sqlx::query!("SELECT COUNT(*) FROM users") + .fetch_one(&db) + .await + .unwrap() + .count + .unwrap_or(0) as i32; + + let mod_count = sqlx::query!("SELECT COUNT(*) FROM mods") + .fetch_one(&db) + .await + .unwrap() + .count + .unwrap_or(0) as i32; let mut res = String::new(); res.push_str(""); @@ -105,8 +116,8 @@ async fn index() -> impl IntoResponse { res.push_str("
"); - res.push_str(&format!("

GraphiQL

")); - res.push_str(&format!("

Playground

")); + res.push_str("

GraphiQL

"); + res.push_str("

Playground

"); res.push_str("
"); @@ -131,26 +142,31 @@ async fn main() -> anyhow::Result<()> { let _ = std::fs::create_dir(Path::new("./data/cdn")); let pool = PgPoolOptions::new() - .min_connections(5) - .max_connections(20) - .connect(&std::env::var("DATABASE_URL").unwrap()).await?; + .min_connections(5) + .max_connections(20) + .connect(&std::env::var("DATABASE_URL").unwrap()) + .await?; DB_POOL.set(pool.clone()).unwrap(); //migrate MIGRATOR.run(&pool).await?; - if !std::env::var("NO_MEILI").unwrap_or("false".to_string()).parse::().unwrap_or(false) { + if !std::env::var("NO_MEILI") + .unwrap_or("false".to_string()) + .parse::() + .unwrap_or(false) + { // set meilisearch settings let client = meilisearch_sdk::client::Client::new( std::env::var("MEILI_URL").unwrap(), Some(std::env::var("MEILI_KEY").unwrap()), ); - + let settings = Settings::new() - .with_filterable_attributes(&["category", "supported_versions"]) - .with_searchable_attributes(&["name", "description"]) - .with_sortable_attributes(&["stats.downloads", "created_at", "updated_at"]); + .with_filterable_attributes(["category", "supported_versions"]) + .with_searchable_attributes(["name", "description"]) + .with_sortable_attributes(["stats.downloads", "created_at", "updated_at"]); client .index(format!( "{}_mods", @@ -163,19 +179,15 @@ async fn main() -> anyhow::Result<()> { MEILI_CONN.set(client).unwrap(); } + let schema = Schema::build(Query, EmptyMutation, EmptySubscription) + .data(pool) + .finish(); + let app = Route::new() .at( "/graphql", - get(GraphQL::new(Schema::new( - Query, - EmptyMutation, - EmptySubscription, - ))) - .post(GraphQL::new(Schema::new( - Query, - EmptyMutation, - EmptySubscription, - ))), + get(GraphQL::new(schema.clone())) + .post(GraphQL::new(schema)), ) .at("/graphiql", get(graphiql_route)) .at("/playground", get(playground_route)) diff --git a/src/entities.rs b/src/models.rs similarity index 68% rename from src/entities.rs rename to src/models.rs index 565d13a..c2f2a9c 100644 --- a/src/entities.rs +++ b/src/models.rs @@ -1,46 +1,48 @@ -use chrono::{DateTime, Utc}; -use serde::{Serialize, Deserialize}; -use uuid::Uuid; +#![allow(non_camel_case_types)] + +use chrono::NaiveDateTime; +use serde::{Deserialize, Serialize}; +use sqlx::types::Uuid; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct BeatSaberVersion { +pub struct dBeatSaberVersion { pub id: Uuid, - pub ver: String + pub ver: String, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Category { +pub struct dCategory { pub id: Uuid, pub name: String, - pub description: String + pub description: String, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct FkModBeatSaberVersion { +pub struct dFkModBeatSaberVersion { pub mod_id: Uuid, - pub beat_saber_version_id: Uuid + pub beat_saber_version_id: Uuid, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct ModStats { +pub struct dModStats { pub id: Uuid, - pub downloads: i32 + pub downloads: i32, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct VersionStats { +pub struct dVersionStat { pub id: Uuid, - pub downloads: i32 + pub downloads: i32, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct FkModVersions { +pub struct dFkModVersion { pub mod_id: Uuid, - pub version_id: Uuid + pub version_id: Uuid, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Mod { +pub struct dMod { pub id: Uuid, pub slug: String, pub name: String, @@ -51,18 +53,18 @@ pub struct Mod { pub author: Uuid, pub category: Uuid, pub stats: Uuid, - pub created_at: DateTime, - pub updated_at: DateTime + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct FkUserMods { +pub struct dFkUserMod { pub user_id: Uuid, - pub mod_id: Uuid + pub mod_id: Uuid, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct User { +pub struct dUser { pub id: Uuid, pub github_id: i32, pub username: String, @@ -73,30 +75,30 @@ pub struct User { pub banner: Option, pub permissions: i32, pub api_key: Uuid, - pub created_at: DateTime, - pub updated_at: DateTime + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct FkVersionBeatSaberVersions { +pub struct dFkVersionBeatSaberVersion { pub version_id: Uuid, - pub beat_saber_version_id: Uuid + pub beat_saber_version_id: Uuid, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct VersionConflicts { +pub struct dVersionConflict { pub version_id: Uuid, - pub dependent: Uuid + pub dependent: Uuid, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct VersionDependents { +pub struct dVersionDependent { pub version_id: Uuid, - pub dependent: Uuid + pub dependent: Uuid, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Versions { +pub struct dVersion { pub id: Uuid, pub mod_id: Uuid, pub version: String, @@ -104,5 +106,5 @@ pub struct Versions { pub stats: Uuid, pub artifact_hash: String, pub download_url: String, - pub created_at: DateTime + pub created_at: NaiveDateTime, } diff --git a/src/mods.rs b/src/mods.rs index d0a40dc..11a6140 100644 --- a/src/mods.rs +++ b/src/mods.rs @@ -1,26 +1,25 @@ use std::vec; -use async_graphql::{SimpleObject, FieldError, FieldResult, Error}; +use async_graphql::{Error, FieldError, FieldResult, SimpleObject}; use chrono::{DateTime, Utc}; use forge_lib::structs::v1::{unpack_v1_forgemod, ForgeModTypes}; // use juniper::{graphql_value, FieldError, FieldResult, GraphQLObject}; -use poem::{handler, Response, Request, http::StatusCode}; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, Set, - TransactionTrait, -}; +use poem::{handler, http::StatusCode, Request, Response}; + use semver::Version; use serde::{Deserialize, Serialize}; +use sqlx::PgPool; use uuid::Uuid; -use entity::prelude::*; use meilisearch_entity::prelude::*; use crate::{ auth::{validate_permissions, Authorization, Permission}, - versions::{self, GVersion}, DB_POOL, + models, + versions::{self, GVersion}, + DB_POOL, }; #[derive(SimpleObject, Debug, Deserialize, Serialize, Clone)] @@ -55,13 +54,27 @@ pub struct ModAuthor { } impl Mod { - async fn from_db_mod( - db: &DatabaseConnection, - m: entity::mods::Model, - ) -> Result { - let category = Categories::find_by_id(m.category).one(db).await?.unwrap(); - let stats = ModStats::find_by_id(m.stats).one(db).await?.unwrap(); - let author = Users::find_by_id(m.author).one(db).await?.unwrap(); + async fn from_db_mod(db: &PgPool, m: models::dMod) -> Result { + let category = sqlx::query_as!( + models::dCategory, + "SELECT * FROM categories WHERE id = $1", + m.category + ) + .fetch_one(db) + .await? + .clone(); + let stats = sqlx::query_as!( + models::dModStats, + "SELECT * FROM mod_stats WHERE id = $1", + m.stats + ) + .fetch_one(db) + .await? + .clone(); + let author = sqlx::query_as!(models::dUser, "SELECT * FROM users WHERE id = $1", m.author) + .fetch_one(db) + .await? + .clone(); Ok(Mod { id: Uuid::from_bytes(*m.id.as_bytes()), slug: m.slug, @@ -108,30 +121,27 @@ pub struct ModCategory { } pub async fn find_all( - db: &DatabaseConnection, + db: &PgPool, limit: i32, offset: i32, version: Option, ) -> FieldResult> { - let limit = limit as u64; - let offset = offset as u64; + let limit = limit as i64; + let offset = offset as i64; if let Some(version) = version { - let verid = BeatSaberVersions::find() - .filter(entity::beat_saber_versions::Column::Ver.eq(version)) - .one(db) - .await? - .unwrap() - .id; + let verid = sqlx::query_as!( + models::dBeatSaberVersion, + "SELECT * FROM beat_saber_versions WHERE ver = $1", + version + ) + .fetch_one(db) + .await? + .id; - let mods = ModBeatSaberVersions::find() - .filter(entity::mod_beat_saber_versions::Column::BeatSaberVersionId.eq(verid)) - .find_also_related(Mods) - .all(db) - .await? - .iter() - .map(|v| v.1.clone().unwrap()) - .collect::>(); + let mods = sqlx::query_as!(models::dMod, "SELECT * FROM mods WHERE id IN (SELECT mod_id FROM mod_beat_saber_versions WHERE beat_saber_version_id = $1) LIMIT $2 OFFSET $3", verid, limit, offset) + .fetch_all(db) + .await?.to_vec(); let mut r = vec![]; for m in mods { @@ -139,7 +149,14 @@ pub async fn find_all( } Ok(r) } else { - let mods = Mods::find().limit(limit).offset(offset).all(db).await?; + let mods = sqlx::query_as!( + models::dMod, + "SELECT * FROM mods LIMIT $1 OFFSET $2", + limit, + offset + ) + .fetch_all(db) + .await?.to_vec(); let mut r = vec![]; for m in mods { @@ -149,41 +166,42 @@ pub async fn find_all( } } -pub async fn find_by_id(db: &DatabaseConnection, id: Uuid) -> FieldResult { - let id = sea_orm::prelude::Uuid::from_bytes(*id.as_bytes()); - let m = Mods::find_by_id(id).one(db).await?; +pub async fn find_by_id(db: &PgPool, id: Uuid) -> FieldResult { + let m = sqlx::query_as!( + models::dMod, + "SELECT * FROM mods WHERE id = $1", + sqlx::types::Uuid::from_bytes(*id.as_bytes()) + ) + .fetch_optional(db) + .await?; if let Some(m) = m { Mod::from_db_mod(db, m).await } else { - Err(Error::new( - "Mod not found", - )) + Err(Error::new("Mod not found")) } } -pub async fn find_by_slug(db: &DatabaseConnection, slug: String) -> FieldResult { - let m = Mods::find() - .filter(entity::mods::Column::Slug.eq(slug)) - .one(db) +pub async fn find_by_slug(db: &PgPool, slug: String) -> FieldResult { + let m = sqlx::query_as!(models::dMod, "SELECT * FROM mods WHERE slug = $1", slug) + .fetch_optional(db) .await?; if let Some(m) = m { Mod::from_db_mod(db, m).await } else { - Err(Error::new( - "Mod not found", - )) + Err(Error::new("Mod not found")) } } -pub async fn find_by_author(db: &DatabaseConnection, author: Uuid) -> FieldResult> { - let author = sea_orm::prelude::Uuid::from_bytes(*author.as_bytes()); - - let mods = Mods::find() - .filter(entity::mods::Column::Author.eq(author)) - .all(db) - .await?; +pub async fn find_by_author(db: &PgPool, author: Uuid) -> FieldResult> { + let mods = sqlx::query_as!( + models::dMod, + "SELECT * FROM mods WHERE author = $1", + sqlx::types::Uuid::from_bytes(*author.as_bytes()) + ) + .fetch_all(db) + .await?.to_vec(); let mut r = vec![]; for m in mods { @@ -193,10 +211,7 @@ pub async fn find_by_author(db: &DatabaseConnection, author: Uuid) -> FieldResul } #[handler] -pub async fn create_mod( - req: &Request, - body: Vec, -) -> Response { +pub async fn create_mod(req: &Request, body: Vec) -> Response { let db = DB_POOL.get().unwrap().clone(); let auth = req @@ -207,14 +222,18 @@ pub async fn create_mod( .unwrap(); let auser; if auth.starts_with("Bearer") { - let auth = Authorization::parse(Some(auth.split(" ").collect::>()[1].to_string())); + let auth = Authorization::parse(Some(auth.split(' ').collect::>()[1].to_string())); let user = auth.get_user(&db).await.unwrap(); if !validate_permissions(&user, Permission::CREATE_MOD).await { - return Response::builder().status(StatusCode::UNAUTHORIZED).body("Unauthorized"); + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body("Unauthorized"); } auser = user; } else { - return Response::builder().status(StatusCode::UNAUTHORIZED).body("Unauthorized"); + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body("Unauthorized"); } // let mut buf = Vec::new(); @@ -222,123 +241,191 @@ pub async fn create_mod( // while let Some(item) = payload.next().await { // let item = item.unwrap(); // buf.extend_from_slice(&item); - // + // let forgemod = { let fm = unpack_v1_forgemod(&*body).unwrap(); match fm { ForgeModTypes::Mod(fm) => fm, - _ => return Response::builder().status(StatusCode::BAD_REQUEST).body("Invalid ForgeMod"), + _ => { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body("Invalid ForgeMod") + } } }; let manifest = forgemod.manifest.inner.clone(); - let db_cata = Categories::find() - .filter(entity::categories::Column::Name.eq(manifest.category.clone().to_string())) - .one(&db) - .await - .unwrap(); + // let db_cata = Categories::find() + // .filter(entity::categories::Column::Name.eq(manifest.category.clone().to_string())) + // .one(&db) + // .await + // .unwrap(); + let db_cata = sqlx::query_as!( + models::dCategory, + "SELECT * FROM categories WHERE name = $1", + manifest.category.clone().to_string() + ) + .fetch_optional(&db) + .await + .unwrap(); // if cata does not exist, default to other let db_cata = if let Some(db_cata) = db_cata { db_cata } else { - Categories::find() - .filter(entity::categories::Column::Name.eq("other")) - .one(&db) - .await - .unwrap() - .unwrap() - }; - - let v_req = manifest.game_version.clone(); - let vers = BeatSaberVersions::find() - .all(&db) + // Categories::find() + // .filter(entity::categories::Column::Name.eq("other")) + // .one(&db) + // .await + // .unwrap() + // .unwrap() + sqlx::query_as!( + models::dCategory, + "SELECT * FROM categories WHERE name = 'other'" + ) + .fetch_one(&db) .await .unwrap() - .into_iter() - .filter(|v| v_req.matches(&Version::parse(&v.ver).unwrap())) - .collect::>(); + }; - if vers.len() == 0 { - return Response::builder().status(StatusCode::BAD_REQUEST).body("No supported versions"); + let v_req = manifest.game_version.clone(); + // let vers = BeatSaberVersions::find() + // .all(&db) + // .await + // .unwrap() + // .into_iter() + // .filter(|v| v_req.matches(&Version::parse(&v.ver).unwrap())) + // .collect::>(); + let vers = sqlx::query_as!( + models::dBeatSaberVersion, + "SELECT * FROM beat_saber_versions" + ) + .fetch_all(&db) + .await + .unwrap() + .into_iter() + .filter(|v| v_req.matches(&Version::parse(&v.ver).unwrap())) + .collect::>(); + + if vers.is_empty() { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body("No supported versions"); } // see if mod exists; if it does add a new version; if it doesn't create a new mod - let mby_mod = Mods::find() - .filter(entity::mods::Column::Slug.eq(forgemod.manifest._id.clone())) - .one(&db) - .await - .unwrap(); + // let mby_mod = Mods::find() + // .filter(entity::mods::Column::Slug.eq(forgemod.manifest._id.clone())) + // .one(&db) + // .await + // .unwrap(); + let mby_mod = sqlx::query_as!( + models::dMod, + "SELECT * FROM mods WHERE slug = $1", + forgemod.manifest._id.clone() + ) + .fetch_optional(&db) + .await + .unwrap(); let v_id; - let trans = db.begin().await.unwrap(); + let mut trans = db.begin().await.unwrap(); if let Some(db_mod) = mby_mod { let db_mod = db_mod.id; for v in &vers { - let vm = entity::mod_beat_saber_versions::ActiveModel { - mod_id: Set(db_mod), - beat_saber_version_id: Set(v.id), - }; - - //see if vm exists - if ModBeatSaberVersions::find() - .filter(entity::mod_beat_saber_versions::Column::ModId.eq(db_mod)) - .filter(entity::mod_beat_saber_versions::Column::BeatSaberVersionId.eq(v.id)) - .one(&trans) + // let vm = entity::mod_beat_saber_versions::ActiveModel { + // mod_id: Set(db_mod), + // beat_saber_version_id: Set(v.id), + // }; + + // //see if vm exists + // if ModBeatSaberVersions::find() + // .filter(entity::mod_beat_saber_versions::Column::ModId.eq(db_mod)) + // .filter(entity::mod_beat_saber_versions::Column::BeatSaberVersionId.eq(v.id)) + // .one(&trans) + // .await + // .unwrap() + // .is_none() + // { + // vm.insert(&trans).await.unwrap(); + // } + let vm = sqlx::query_as!(models::dFkModBeatSaberVersion, "SELECT * FROM mod_beat_saber_versions WHERE mod_id = $1 AND beat_saber_version_id = $2", db_mod, v.id) + .fetch_optional(&mut *trans) .await - .unwrap() - .is_none() - { - vm.insert(&trans).await.unwrap(); + .unwrap(); + + if vm.is_none() { + sqlx::query!("INSERT INTO mod_beat_saber_versions (mod_id, beat_saber_version_id) VALUES ($1, $2)", db_mod, v.id) + .execute(&mut *trans).await.unwrap(); } } - let version_stats = entity::version_stats::ActiveModel { - ..Default::default() - } - .insert(&trans) - .await - .unwrap() - .id; + let version_stats = sqlx::query!("INSERT INTO version_stats DEFAULT VALUES RETURNING id") + .fetch_one(&mut *trans) + .await + .unwrap() + .id; - let version = entity::versions::ActiveModel { - mod_id: Set(db_mod), - version: Set(manifest.version.clone().to_string()), - stats: Set(version_stats), - //todo: artifact hash - artifact_hash: Set("".to_string()), - //todo: download url - download_url: Set(format!( - "{}/cdn/{}@{}", - std::env::var("PUBLIC_URL").unwrap(), - forgemod.manifest._id, - manifest.version.clone().to_string() - )), - ..Default::default() - } - .insert(&trans) - .await - .unwrap() - .id; + // let version = entity::versions::ActiveModel { + // mod_id: Set(db_mod), + // version: Set(manifest.version.clone().to_string()), + // stats: Set(version_stats), + // //todo: artifact hash + // artifact_hash: Set("".to_string()), + // //todo: download url + // download_url: Set(format!( + // "{}/cdn/{}@{}", + // std::env::var("PUBLIC_URL").unwrap(), + // forgemod.manifest._id, + // manifest.version.clone().to_string() + // )), + // ..Default::default() + // } + // .insert(&trans) + // .await + // .unwrap() + // .id; + let version = sqlx::query!("INSERT INTO versions (mod_id, version, stats, artifact_hash, download_url) VALUES ($1, $2, $3, $4, $5) RETURNING id", db_mod, manifest.version.clone().to_string(), version_stats, "", format!("{}/cdn/{}@{}", std::env::var("PUBLIC_URL").unwrap(),forgemod.manifest._id,manifest.version.clone().to_string())).fetch_one(&mut *trans).await.unwrap().id; for v in &vers { - let _ = entity::version_beat_saber_versions::ActiveModel { - version_id: Set(version), - beat_saber_version_id: Set(v.id), - } - .insert(&trans) - .await - .unwrap(); + // let _ = entity::version_beat_saber_versions::ActiveModel { + // version_id: Set(version), + // beat_saber_version_id: Set(v.id), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!("INSERT INTO version_beat_saber_versions (version_id, beat_saber_version_id) VALUES ($1, $2)", version, v.id).execute(&mut *trans).await.unwrap(); } + sqlx::query!( + "INSERT INTO mod_versions (mod_id, version_id) VALUES ($1, $2)", + db_mod, + version + ) + .execute(&mut *trans) + .await + .unwrap(); + for conflict in manifest.conflicts { - let c_ver = Versions::find() - .filter(entity::versions::Column::ModId.eq(db_mod)) - .all(&trans) + // let c_ver = Versions::find() + // .filter(entity::versions::Column::ModId.eq(db_mod)) + // .all(&trans) + // .await + // .unwrap() + // .into_iter() + // .filter(|c| { + // conflict + // .version + // .matches(&Version::parse(&c.version).unwrap()) + // }) + // .collect::>(); + let c_ver = sqlx::query!("SELECT * FROM versions WHERE (mod_id = $1)", db_mod) + .fetch_all(&mut *trans) .await .unwrap() .into_iter() @@ -350,20 +437,39 @@ pub async fn create_mod( .collect::>(); for c in c_ver { - let _ = entity::version_conflicts::ActiveModel { - version_id: Set(version), - dependent: Set(c.id), - } - .insert(&trans) + // let _ = entity::version_conflicts::ActiveModel { + // version_id: Set(version), + // dependent: Set(c.id), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!( + "INSERT INTO version_conflicts (version_id, dependent) VALUES ($1, $2)", + version, + c.id + ) + .execute(&mut *trans) .await .unwrap(); } } for dependent in manifest.depends { - let d_ver = Versions::find() - .filter(entity::versions::Column::ModId.eq(db_mod)) - .all(&trans) + // let d_ver = Versions::find() + // .filter(entity::versions::Column::ModId.eq(db_mod)) + // .all(&trans) + // .await + // .unwrap() + // .into_iter() + // .filter(|d| { + // dependent + // .version + // .matches(&Version::parse(&d.version).unwrap()) + // }) + // .collect::>(); + let d_ver = sqlx::query!("SELECT * FROM versions WHERE (mod_id = $1)", db_mod) + .fetch_all(&mut *trans) .await .unwrap() .into_iter() @@ -375,108 +481,150 @@ pub async fn create_mod( .collect::>(); for d in d_ver { - let _ = entity::version_dependents::ActiveModel { - version_id: Set(version), - dependent: Set(d.id), - } - .insert(&trans) + // let _ = entity::version_dependents::ActiveModel { + // version_id: Set(version), + // dependent: Set(d.id), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!( + "INSERT INTO version_dependents (version_id, dependent) VALUES ($1, $2)", + version, + d.id + ) + .execute(&mut *trans) .await .unwrap(); } } v_id = version; } else { - let mod_stats = entity::mod_stats::ActiveModel { - ..Default::default() - } - .insert(&trans) - .await - .unwrap() - .id; - - let db_mod = entity::mods::ActiveModel { - slug: Set(forgemod.manifest._id.clone()), - name: Set(manifest.name.clone()), - author: Set(auser.id), - description: Set(Some(manifest.description.clone())), - website: Set(Some(manifest.website.clone())), - category: Set(db_cata.id), - stats: Set(mod_stats), - ..Default::default() - } - .insert(&trans) - .await - .unwrap() - .id; + // let mod_stats = entity::mod_stats::ActiveModel { + // ..Default::default() + // } + // .insert(&trans) + // .await + // .unwrap() + // .id; + let mod_stats = sqlx::query!("INSERT INTO mod_stats DEFAULT VALUES RETURNING id") + .fetch_one(&mut *trans) + .await + .unwrap() + .id; - entity::user_mods::ActiveModel { - user_id: Set(auser.id), - mod_id: Set(db_mod), - } - .insert(&trans) + // let db_mod = entity::mods::ActiveModel { + // slug: Set(forgemod.manifest._id.clone()), + // name: Set(manifest.name.clone()), + // author: Set(auser.id), + // description: Set(Some(manifest.description.clone())), + // website: Set(Some(manifest.website.clone())), + // category: Set(db_cata.id), + // stats: Set(mod_stats), + // ..Default::default() + // } + // .insert(&trans) + // .await + // .unwrap() + // .id; + let db_mod = sqlx::query!("INSERT INTO mods (slug, name, author, description, website, category, stats) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id", forgemod.manifest._id.clone(), manifest.name.clone(), auser.id, manifest.description.clone(), manifest.website.clone(), db_cata.id, mod_stats).fetch_one(&mut *trans).await.unwrap().id; + + // entity::user_mods::ActiveModel { + // user_id: Set(auser.id), + // mod_id: Set(db_mod), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!( + "INSERT INTO user_mods (user_id, mod_id) VALUES ($1, $2)", + auser.id, + db_mod + ) + .execute(&mut *trans) .await .unwrap(); for v in &vers { - let _ = entity::mod_beat_saber_versions::ActiveModel { - mod_id: Set(db_mod), - beat_saber_version_id: Set(v.id), - } - .insert(&trans) - .await - .unwrap(); + // let _ = entity::mod_beat_saber_versions::ActiveModel { + // mod_id: Set(db_mod), + // beat_saber_version_id: Set(v.id), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!("INSERT INTO mod_beat_saber_versions (mod_id, beat_saber_version_id) VALUES ($1, $2)", db_mod, v.id).execute(&mut *trans).await.unwrap(); } - let version_stats = entity::version_stats::ActiveModel { - ..Default::default() - } - .insert(&trans) - .await - .unwrap() - .id; + // let version_stats = entity::version_stats::ActiveModel { + // ..Default::default() + // } + // .insert(&trans) + // .await + // .unwrap() + // .id; + let version_stats = sqlx::query!("INSERT INTO version_stats DEFAULT VALUES RETURNING id") + .fetch_one(&mut *trans) + .await + .unwrap() + .id; - let version = entity::versions::ActiveModel { - mod_id: Set(db_mod), - version: Set(manifest.version.clone().to_string()), - stats: Set(version_stats), - //todo: artifact hash - artifact_hash: Set("".to_string()), - //todo: download url - download_url: Set(format!( - "{}/cdn/{}@{}", - std::env::var("PUBLIC_URL").unwrap(), - forgemod.manifest._id, - manifest.version.clone().to_string() - )), - ..Default::default() - } - .insert(&trans) - .await - .unwrap() - .id; + // let version = entity::versions::ActiveModel { + // mod_id: Set(db_mod), + // version: Set(manifest.version.clone().to_string()), + // stats: Set(version_stats), + // //todo: artifact hash + // artifact_hash: Set("".to_string()), + // //todo: download url + // download_url: Set(format!( + // "{}/cdn/{}@{}", + // std::env::var("PUBLIC_URL").unwrap(), + // forgemod.manifest._id, + // manifest.version.clone().to_string() + // )), + // ..Default::default() + // } + // .insert(&trans) + // .await + // .unwrap() + // .id; + let version = sqlx::query!("INSERT INTO versions (mod_id, version, stats, artifact_hash, download_url) VALUES ($1, $2, $3, $4, $5) RETURNING id", db_mod, manifest.version.clone().to_string(), version_stats, "", format!("{}/cdn/{}@{}", std::env::var("PUBLIC_URL").unwrap(),forgemod.manifest._id,manifest.version.clone().to_string())).fetch_one(&mut *trans).await.unwrap().id; for v in &vers { - let _ = entity::version_beat_saber_versions::ActiveModel { - version_id: Set(version), - beat_saber_version_id: Set(v.id), - } - .insert(&trans) - .await - .unwrap(); + // let _ = entity::version_beat_saber_versions::ActiveModel { + // version_id: Set(version), + // beat_saber_version_id: Set(v.id), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!("INSERT INTO version_beat_saber_versions (version_id, beat_saber_version_id) VALUES ($1, $2)", version, v.id).execute(&mut *trans).await.unwrap(); } - entity::mod_versions::ActiveModel { - mod_id: Set(db_mod), - version_id: Set(version), - } - .insert(&trans) + sqlx::query!( + "INSERT INTO mod_versions (mod_id, version_id) VALUES ($1, $2)", + db_mod, + version + ) + .execute(&mut *trans) .await .unwrap(); for conflict in manifest.conflicts { - let c_ver = Versions::find() - .filter(entity::versions::Column::ModId.eq(db_mod)) - .all(&trans) + // let c_ver = Versions::find() + // .filter(entity::versions::Column::ModId.eq(db_mod)) + // .all(&trans) + // .await + // .unwrap() + // .into_iter() + // .filter(|c| { + // conflict + // .version + // .matches(&Version::parse(&c.version).unwrap()) + // }) + // .collect::>(); + let c_ver = sqlx::query!("SELECT * FROM versions WHERE (mod_id = $1)", db_mod) + .fetch_all(&mut *trans) .await .unwrap() .into_iter() @@ -488,20 +636,39 @@ pub async fn create_mod( .collect::>(); for c in c_ver { - let _ = entity::version_conflicts::ActiveModel { - version_id: Set(version), - dependent: Set(c.id), - } - .insert(&trans) + // let _ = entity::version_conflicts::ActiveModel { + // version_id: Set(version), + // dependent: Set(c.id), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!( + "INSERT INTO version_conflicts (version_id, dependent) VALUES ($1, $2)", + version, + c.id + ) + .execute(&mut *trans) .await .unwrap(); } } for dependent in manifest.depends { - let d_ver = Versions::find() - .filter(entity::versions::Column::ModId.eq(db_mod)) - .all(&trans) + // let d_ver = Versions::find() + // .filter(entity::versions::Column::ModId.eq(db_mod)) + // .all(&trans) + // .await + // .unwrap() + // .into_iter() + // .filter(|d| { + // dependent + // .version + // .matches(&Version::parse(&d.version).unwrap()) + // }) + // .collect::>(); + let d_ver = sqlx::query!("SELECT * FROM versions WHERE (mod_id = $1)", db_mod) + .fetch_all(&mut *trans) .await .unwrap() .into_iter() @@ -513,11 +680,19 @@ pub async fn create_mod( .collect::>(); for d in d_ver { - let _ = entity::version_dependents::ActiveModel { - version_id: Set(version), - dependent: Set(d.id), - } - .insert(&trans) + // let _ = entity::version_dependents::ActiveModel { + // version_id: Set(version), + // dependent: Set(d.id), + // } + // .insert(&trans) + // .await + // .unwrap(); + sqlx::query!( + "INSERT INTO version_dependents (version_id, dependent) VALUES ($1, $2)", + version, + d.id + ) + .execute(&mut *trans) .await .unwrap(); } @@ -525,12 +700,20 @@ pub async fn create_mod( v_id = version; } - let db_mod = Mods::find() - .filter(entity::mods::Column::Slug.eq(forgemod.manifest._id.clone())) - .one(&trans) - .await - .unwrap() - .unwrap(); + // let db_mod = Mods::find() + // .filter(entity::mods::Column::Slug.eq(forgemod.manifest._id.clone())) + // .one(&trans) + // .await + // .unwrap() + // .unwrap(); + let db_mod = sqlx::query_as!( + models::dMod, + "SELECT * FROM mods WHERE (slug = $1)", + forgemod.manifest._id.clone() + ) + .fetch_one(&mut *trans) + .await + .unwrap(); let _ = std::fs::create_dir(format!("./data/cdn/{}", &db_mod.id)); std::fs::write(format!("./data/cdn/{}/{}.forgemod", &db_mod.id, v_id), body).unwrap(); @@ -543,30 +726,51 @@ pub async fn create_mod( Some(std::env::var("MEILI_KEY").unwrap()), ); - let mod_vers = ModVersions::find() - .filter(entity::mod_versions::Column::ModId.eq(db_mod.id)) - .find_also_related(Versions) - .all(&db) - .await - .unwrap() - .into_iter() - .map(|(_, v)| Version::parse(&v.unwrap().version).unwrap()) - .collect::>(); - - let supported_versions = ModBeatSaberVersions::find().filter(entity::mod_beat_saber_versions::Column::ModId.eq(db_mod.id)) - .find_also_related(BeatSaberVersions) - .all(&db) - .await - .unwrap() - .into_iter() - .map(|(_, v)| Version::parse(&v.unwrap().ver).unwrap()) - .collect::>(); - - let mod_stats = ModStats::find_by_id(db_mod.stats) - .one(&db) - .await - .unwrap() - .unwrap(); + // let mod_vers = ModVersions::find() + // .filter(entity::mod_versions::Column::ModId.eq(db_mod.id)) + // .find_also_related(Versions) + // .all(&db) + // .await + // .unwrap() + // .into_iter() + // .map(|(_, v)| Version::parse(&v.unwrap().version).unwrap()) + // .collect::>(); + let mod_vers = sqlx::query_as!( + models::dVersion, + "SELECT * FROM versions WHERE (mod_id = $1)", + db_mod.id + ) + .fetch_all(&db) + .await + .unwrap() + .into_iter() + .map(|v| Version::parse(&v.version).unwrap()) + .collect::>(); + + // let supported_versions = ModBeatSaberVersions::find() + // .filter(entity::mod_beat_saber_versions::Column::ModId.eq(db_mod.id)) + // .find_also_related(BeatSaberVersions) + // .all(&db) + // .await + // .unwrap() + // .into_iter() + // .map(|(_, v)| Version::parse(&v.unwrap().ver).unwrap()) + // .collect::>(); + let supported_versions = sqlx::query_as!(models::dBeatSaberVersion, "SELECT * FROM beat_saber_versions WHERE id IN (SELECT beat_saber_version_id FROM mod_beat_saber_versions WHERE mod_id = $1)", db_mod.id).fetch_all(&db).await.unwrap().into_iter().map(|v| Version::parse(&v.ver).unwrap()).collect::>(); + + // let mod_stats = ModStats::find_by_id(db_mod.stats) + // .one(&db) + // .await + // .unwrap() + // .unwrap(); + let mod_stats = sqlx::query_as!( + models::dModStats, + "SELECT * FROM mod_stats WHERE id = $1", + db_mod.stats + ) + .fetch_one(&db) + .await + .unwrap(); let meilimod = MeiliMod { id: db_mod.id, @@ -589,7 +793,16 @@ pub async fn create_mod( updated_at: db_mod.updated_at.and_utc().timestamp(), supported_versions, }; - client.index(format!("{}mods", std::env::var("MEILI_PREFIX").unwrap_or("".to_string()))).add_or_replace(&[meilimod], None).await.unwrap(); + client + .index(format!( + "{}mods", + std::env::var("MEILI_PREFIX").unwrap_or("".to_string()) + )) + .add_or_replace(&[meilimod], None) + .await + .unwrap(); - Response::builder().status(StatusCode::CREATED).body("Created") + Response::builder() + .status(StatusCode::CREATED) + .body("Created") } diff --git a/src/schema.rs b/src/schema.rs index f99e407..425ae84 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,7 +1,6 @@ -use entity::prelude::*; use async_graphql::*; -use sea_orm::{EntityTrait, DatabaseConnection}; +use sqlx::PgPool; use uuid::Uuid; use crate::auth::Authorization; @@ -13,19 +12,26 @@ pub struct Query; #[Object] impl Query { - async fn user_by_id<'ctx>(&self, ctx: &Context<'ctx>, id: Uuid, auth: Option) -> Result { - let db = ctx.data::()?.clone(); - users::find_by_id(&db, id, Authorization::parse(auth)).await + async fn user_by_id<'ctx>( + &self, + ctx: &Context<'ctx>, + id: Uuid, + auth: Option, + ) -> Result { + let db = ctx.data::()?; + users::find_by_id(db, id, Authorization::parse(auth)).await } - async fn users<'ctx>(&self, ctx: &Context<'ctx>, - #[graphql(validator(maximum=10))] limit: Option, + async fn users<'ctx>( + &self, + ctx: &Context<'ctx>, + #[graphql(validator(maximum = 10))] limit: Option, offset: Option, auth: Option, ) -> Result> { - let db = ctx.data::()?.clone(); + let db = ctx.data::()?; users::find_all( - &db, + db, limit.unwrap_or(10), offset.unwrap_or(0), Authorization::parse(auth), @@ -33,56 +39,59 @@ impl Query { .await } - async fn mods<'ctx>(&self, ctx: &Context<'ctx>, - #[graphql(validator(maximum=10))] limit: Option, + async fn mods<'ctx>( + &self, + ctx: &Context<'ctx>, + #[graphql(validator(maximum = 10))] limit: Option, offset: Option, version: Option, ) -> Result> { - let db = ctx.data::()?.clone(); + let db = ctx.data::()?; - mods::find_all(&db, limit.unwrap_or(10), offset.unwrap_or(0), version).await + mods::find_all(db, limit.unwrap_or(10), offset.unwrap_or(0), version).await } async fn mod_by_id<'ctx>(&self, ctx: &Context<'ctx>, id: Uuid) -> Result { - let db = ctx.data::()?.clone(); + let db = ctx.data::()?; - mods::find_by_id(&db, id).await + mods::find_by_id(db, id).await } async fn mod_by_slug<'ctx>(&self, ctx: &Context<'ctx>, slug: String) -> Result { - let db = ctx.data::()?.clone(); + let db = ctx.data::()?; - mods::find_by_slug(&db, slug).await + mods::find_by_slug(db, slug).await } async fn mod_by_author<'ctx>(&self, ctx: &Context<'ctx>, id: Uuid) -> Result> { - let db = ctx.data::()?.clone(); + let db = ctx.data::()?; - mods::find_by_author(&db, id).await + mods::find_by_author(db, id).await } async fn categories<'ctx>(&self, ctx: &Context<'ctx>) -> Result> { - let db = ctx.data::()?.clone(); + let db = ctx.data::()?; - Ok(Categories::find() - .all(&db) - .await - .unwrap() - .iter() - .map(|c| GCategory { - name: c.name.clone(), - description: c.description.clone(), - }) - .collect::>()) + Ok( + sqlx::query_as!(GCategory, "SELECT name, description FROM categories") + .fetch_all(db) + .await?, + ) } async fn beat_saber_versions<'ctx>(&self, ctx: &Context<'ctx>) -> Result> { - let db = ctx.data::()?.clone(); - - Ok(BeatSaberVersions::find() - .all(&db) - .await - .unwrap() + let db = ctx.data::()?; + + // Ok(BeatSaberVersions::find() + // .all(&db) + // .await + // .unwrap() + // .iter() + // .map(|v| v.ver.clone()) + // .collect::>()) + Ok(sqlx::query!("SELECT * FROM beat_saber_versions") + .fetch_all(db) + .await? .iter() .map(|v| v.ver.clone()) .collect::>()) diff --git a/src/users.rs b/src/users.rs index 69d863b..9fc825a 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,17 +1,22 @@ -use async_graphql::{SimpleObject, FieldResult, FieldError, Error}; +use async_graphql::{Error, FieldError, FieldResult, SimpleObject}; use chrono::{DateTime, Utc}; -use entity::prelude::*; -use poem::{handler, web::{Query, Json}, Response, Request, IntoResponse, http::StatusCode}; -use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set, DatabaseConnection}; +use poem::{ + handler, + http::StatusCode, + web::{Json, Query}, + IntoResponse, Request, Response, +}; use serde::{Deserialize, Serialize}; use serde_json::json; +use sqlx::PgPool; use tracing::debug; use uuid::Uuid; use crate::{ auth::{validate_permissions, Authorization, JWTAuth, Permission}, + models, mods::{self, Mod}, - KEY, DB_POOL + DB_POOL, KEY, }; #[derive(SimpleObject, Debug, Deserialize, Serialize, Clone)] @@ -38,7 +43,7 @@ pub struct User { } impl User { - async fn from_db_user(db: &DatabaseConnection, u: entity::users::Model) -> Result { + async fn from_db_user(db: &PgPool, u: models::dUser) -> Result { Ok(User { id: Uuid::from_bytes(*u.id.as_bytes()), github_id: u.github_id.to_string(), @@ -60,19 +65,27 @@ impl User { } pub async fn find_all( - db: &DatabaseConnection, + db: &PgPool, limit: i32, offset: i32, auth: Authorization, ) -> FieldResult> { - let limit = limit as u64; - let offset = offset as u64; - - let users = Users::find() - .limit(Some(limit)) - .offset(Some(offset)) - .all(db) - .await?; + let limit = limit as i64; + let offset = offset as i64; + + // let users = Users::find() + // .limit(Some(limit)) + // .offset(Some(offset)) + // .all(db) + // .await?; + let users = sqlx::query_as!( + models::dUser, + "SELECT * FROM users LIMIT $1 OFFSET $2", + limit, + offset + ) + .fetch_all(db) + .await?; let auser = auth.get_user(db).await; @@ -95,7 +108,9 @@ pub async fn find_all( users .iter_mut() .map(move |user| async move { - if usr.id.as_bytes() != user.id.as_bytes() && !validate_permissions(user.clone(), Permission::VIEW_OTHER).await { + if usr.id.as_bytes() != user.id.as_bytes() + && !validate_permissions(user.clone(), Permission::VIEW_OTHER).await + { user.email = None; user.api_key = None; } @@ -115,19 +130,24 @@ pub async fn find_all( ) .await; } - + Ok(users) } -pub async fn find_by_id(db: &DatabaseConnection, _id: Uuid, auth: Authorization) -> FieldResult { - let id = sea_orm::prelude::Uuid::from_bytes(*_id.as_bytes()); +pub async fn find_by_id(db: &PgPool, _id: Uuid, auth: Authorization) -> FieldResult { + // let id = sea_orm::prelude::Uuid::from_bytes(*_id.as_bytes()); - let user = Users::find_by_id(id).one(db).await?; + // let user = Users::find_by_id(id).one(db).await?; + let user = sqlx::query_as!( + models::dUser, + "SELECT * FROM users WHERE id = $1", + sqlx::types::Uuid::from_bytes(*_id.as_bytes()) + ) + .fetch_optional(db) + .await?; if user.is_none() { - return Err(Error::new( - "User not found" - )); + return Err(Error::new("User not found")); } let mut user = User::from_db_user(db, user.unwrap()).await?; @@ -135,7 +155,9 @@ pub async fn find_by_id(db: &DatabaseConnection, _id: Uuid, auth: Authorization) // check auth let auser = auth.get_user(db).await; if let Some(usr) = auser { - if usr.id.as_bytes() != user.id.as_bytes() && !validate_permissions(&user, Permission::VIEW_OTHER).await { + if usr.id.as_bytes() != user.id.as_bytes() + && !validate_permissions(&user, Permission::VIEW_OTHER).await + { user.email = None; user.api_key = None; } @@ -156,7 +178,7 @@ pub async fn user_auth( // info: web::Query, Query(UserAuthReq { code }): Query, ) -> impl IntoResponse { - let db = DB_POOL.get().unwrap().clone(); + let db = DB_POOL.get().unwrap(); let gat = minreq::post("https://github.com/login/oauth/access_token") .with_header("User-Agent", "forge-registry") @@ -183,36 +205,69 @@ pub async fn user_auth( debug!("{}", github_user.as_str().unwrap()); let github_user = serde_json::from_str::(github_user.as_str().unwrap()).unwrap(); - let mby_user = Users::find() - .filter(entity::users::Column::GithubId.eq(github_user.id as i32)) - .one(&db) + // let mby_user = Users::find() + // .filter(entity::users::Column::GithubId.eq(github_user.id as i32)) + // .one(&db) + // .await + // .unwrap(); + + // if mby_user.is_none() { + // let usr = entity::users::ActiveModel { + // github_id: Set(github_user.id as i32), + // username: Set(github_user.login), + // email: Set(github_user.email.unwrap_or("".to_string())), + // bio: Set(github_user.bio), + // avatar: Set(github_user.avatar_url), + // permissions: Set(7), + // ..Default::default() + // }; + + // Users::insert(usr).exec(&db).await.unwrap(); + // } + + // let user = Users::find() + // .filter(entity::users::Column::GithubId.eq(github_user.id as i32)) + // .one(&db) + // .await + // .unwrap() + // .unwrap(); + + let user = sqlx::query_as!( + models::dUser, + "SELECT * FROM users WHERE github_id = $1", + github_user.id as i32 + ) + .fetch_optional(db) + .await + .unwrap(); + + if user.is_none() { + sqlx::query!( + "INSERT INTO users (github_id, username, email, bio, avatar, permissions) VALUES ($1, $2, $3, $4, $5, $6)", + github_user.id as i32, + github_user.login.clone(), + github_user.email.unwrap_or("".to_string()), + github_user.bio, + github_user.avatar_url, + 7 + ) + .execute(db) .await .unwrap(); - - if mby_user.is_none() { - let usr = entity::users::ActiveModel { - github_id: Set(github_user.id as i32), - username: Set(github_user.login), - email: Set(github_user.email.unwrap_or("".to_string())), - bio: Set(github_user.bio), - avatar: Set(github_user.avatar_url), - permissions: Set(7), - ..Default::default() - }; - - Users::insert(usr).exec(&db).await.unwrap(); } - let user = Users::find() - .filter(entity::users::Column::GithubId.eq(github_user.id as i32)) - .one(&db) - .await - .unwrap() - .unwrap(); + let user = sqlx::query_as!( + models::dUser, + "SELECT * FROM users WHERE github_id = $1", + github_user.id as i32 + ) + .fetch_one(db) + .await + .unwrap(); let jwt = JWTAuth::new(user).encode(*KEY.clone()); - Json(json!({ "jwt": jwt })) + Json(json!({ "jwt": jwt })) } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -225,9 +280,7 @@ pub struct GithubUser { } #[handler] -pub async fn get_me( - req: &Request -) -> impl IntoResponse { +pub async fn get_me(req: &Request) -> impl IntoResponse { let db = DB_POOL.get().unwrap().clone(); let auth = req @@ -238,12 +291,14 @@ pub async fn get_me( .unwrap(); let auser; if auth.starts_with("Bearer") { - let auth = Authorization::parse(Some(auth.split(" ").collect::>()[1].to_string())); + let auth = Authorization::parse(Some(auth.split(' ').collect::>()[1].to_string())); let user = auth.get_user(&db).await.unwrap(); auser = user; } else { - return Response::builder().status(StatusCode::UNAUTHORIZED).body("Unauthorized"); + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body("Unauthorized"); } Json(auser).into_response() -} \ No newline at end of file +} diff --git a/src/versions.rs b/src/versions.rs index 23e4044..7809211 100644 --- a/src/versions.rs +++ b/src/versions.rs @@ -1,11 +1,12 @@ -use chrono::{DateTime, Utc}; -use entity::prelude::*; use async_graphql::*; +use chrono::{DateTime, Utc}; -use serde::{Serialize, Deserialize}; -use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, DatabaseConnection}; +use serde::{Deserialize, Serialize}; +use sqlx::PgPool; use uuid::Uuid; +use crate::models; + #[derive(SimpleObject, Debug, Deserialize, Serialize, Clone)] pub struct GVersion { pub id: Uuid, @@ -26,25 +27,35 @@ pub struct GVersionStats { } impl GVersion { - pub async fn from_db_version( - db: &DatabaseConnection, - v: entity::versions::Model, - ) -> Result { - let versions = VersionBeatSaberVersions::find() - .filter(entity::version_beat_saber_versions::Column::VersionId.eq(v.id)) - .find_also_related(BeatSaberVersions) - .all(db) - .await - .unwrap() + pub async fn from_db_version(db: &PgPool, v: models::dVersion) -> Result { + // let versions = VersionBeatSaberVersions::find() + // .filter(entity::version_beat_saber_versions::Column::VersionId.eq(v.id)) + // .find_also_related(BeatSaberVersions) + // .all(db) + // .await + // .unwrap() + // .iter() + // .map(|v| v.1.clone().unwrap().ver) + // .collect::>(); + let versions = sqlx::query!("SELECT * FROM beat_saber_versions WHERE id IN (SELECT beat_saber_version_id FROM version_beat_saber_versions WHERE version_id = $1)", sqlx::types::Uuid::from_bytes(*v.id.as_bytes())) + .fetch_all(db) + .await? .iter() - .map(|v| v.1.clone().unwrap().ver) + .map(|v| v.ver.clone()) .collect::>(); - let stats = VersionStats::find_by_id(v.stats) - .one(db) - .await - .unwrap() - .unwrap(); + // let stats = VersionStats::find_by_id(v.stats) + // .one(db) + // .await + // .unwrap() + // .unwrap(); + let stats = sqlx::query_as!( + models::dVersionStat, + "SELECT * FROM version_stats WHERE id = $1", + sqlx::types::Uuid::from_bytes(*v.stats.as_bytes()) + ) + .fetch_one(db) + .await?; Ok(GVersion { id: Uuid::from_bytes(*v.id.as_bytes()), @@ -61,14 +72,21 @@ impl GVersion { } } -pub async fn find_by_mod_id(db: &DatabaseConnection, id: Uuid) -> FieldResult> { - let id = sea_orm::prelude::Uuid::from_bytes(*id.as_bytes()); +pub async fn find_by_mod_id(db: &PgPool, id: Uuid) -> FieldResult> { + // let id = sea_orm::prelude::Uuid::from_bytes(*id.as_bytes()); - let versions = Versions::find() - .filter(entity::versions::Column::ModId.eq(id)) - .all(db) - .await - .unwrap(); + // let versions = Versions::find() + // .filter(entity::versions::Column::ModId.eq(id)) + // .all(db) + // .await + // .unwrap(); + let versions = sqlx::query_as!( + models::dVersion, + "SELECT * FROM versions WHERE mod_id = $1", + sqlx::types::Uuid::from_bytes(*id.as_bytes()) + ) + .fetch_all(db) + .await?; let mut r = vec![]; for version in versions {