~saiko/modsite

cd733e953863c9c20ffb288355fc045d6dcc983e — 2xsaiko 2 months ago 5a74e82
Finish migration to sqlx
19 files changed, 138 insertions(+), 645 deletions(-)

M migtool/src/apply.rs
M site/Cargo.toml
M site/build.rs
M site/src/db/mod.rs
D site/src/db/models.rs
D site/src/db/schema.patch
D site/src/db/schema.rs
D site/src/db/schema.rs.orig
M site/src/db/types.rs
M site/src/indexer/db.rs
M site/src/indexer/github.rs
M site/src/indexer/maven.rs
M site/src/main.rs
M site/src/modconf/modsrc.rs
M site/src/site/db_access.rs
M site/src/site/main_page.rs
M site/src/site/mod.rs
M site/src/site/mod_page.rs
M srvrc
M migtool/src/apply.rs => migtool/src/apply.rs +1 -1
@@ 21,7 21,7 @@ pub enum ApplyBehavior<'a> {
}

pub async fn apply_migration(db_url: &str, v: u64, b: &ApplyBehavior<'_>, dir: &Path, unapply: bool, pretend: bool) -> anyhow::Result<()> {
    let mut db: PgConnection = PgConnection::connect(db_url).await.unwrap();
    let db: PgConnection = PgConnection::connect(db_url).await.unwrap();

    let mut available: Vec<Migration> = fs::read_dir(dir).await?.filter_map(|entry| {
        let entry = entry.unwrap();

M site/Cargo.toml => site/Cargo.toml +4 -1
@@ 27,4 27,7 @@ num_cpus = "1.13.0"
cmdparser = { git = "https://git.dblsaiko.net/cmdparser.git", default-features = false }
log = "0.4.8"
simplelog = "0.8.0"
itertools = "0.9.0"
\ No newline at end of file
itertools = "0.9.0"

[build-dependencies]
cmdparser = { git = "https://git.dblsaiko.net/cmdparser.git", default-features = false }

M site/build.rs => site/build.rs +19 -1
@@ 1,7 1,25 @@
use std::path::Path;
use std::process::Command;

use cmdparser::{CommandDispatcher, ExecSource, SimpleExecutor};

fn main() {
    let output = Command::new("git").args(&["rev-parse", "HEAD"]).output().unwrap();
    let git_hash = String::from_utf8(output.stdout).unwrap();
    println!("cargo:rustc-env=GIT_HASH={}", git_hash);
}
\ No newline at end of file
    println!("cargo:rustc-env=DATABASE_URL={}", read_config("../srvrc"));
}

#[allow(clippy::single_match)]
fn read_config(path: impl AsRef<Path>) -> String {
    let mut db_url = None;

    let mut cd = CommandDispatcher::new(SimpleExecutor::new(|cmd, args| match cmd {
        "db_url" => db_url = Some(args[0].to_string()),
        _ => {}
    }));
    cd.scheduler().exec_path(path, ExecSource::Other).expect("Could not open config file");
    cd.resume_until_empty();

    db_url.expect("db_url not set in config file")
}

M site/src/db/mod.rs => site/src/db/mod.rs +0 -2
@@ 1,3 1,1 @@
pub mod schema;
pub mod models;
pub mod types;
\ No newline at end of file

D site/src/db/models.rs => site/src/db/models.rs +0 -112
@@ 1,112 0,0 @@
use diesel::PgConnection;
use diesel::prelude::*;
use uuid::Uuid;

use crate::db::types::Environment;

use super::schema::*;

#[derive(Debug, Clone, Queryable, Insertable, AsChangeset)]
#[table_name = "mod_base"]
pub struct ModBase {
    pub uuid: Uuid,
    pub id: String,
    pub display_version: Option<String>,
}

#[derive(Debug, Clone, Queryable, Insertable, AsChangeset)]
#[table_name = "mod_version"]
pub struct ModVersion {
    pub mod_base: Uuid,
    pub version: String,
    pub id: String,
    pub name: Option<String>,
    pub description: Option<String>,
    pub homepage: Option<String>,
    pub issues: Option<String>,
    pub source: Option<String>,
    pub license: Option<String>,
    pub download_link: String,
    pub environment: Environment,
    pub file_version: String,
}

impl ModBase {
    pub fn by_primary_key(k_uuid: &Uuid, c: &PgConnection) -> QueryResult<ModBase> {
        use super::schema::mod_base::dsl::*;
        mod_base
            .filter(uuid.eq(k_uuid))
            .first(c)
    }

    pub fn by_id(k_id: &str, c: &PgConnection) -> QueryResult<ModBase> {
        use super::schema::mod_base::dsl::*;
        mod_base
            .filter(id.eq(k_id))
            .first(c)
    }

    pub fn insert(mut self, c: &PgConnection) -> QueryResult<Self> {
        use super::schema::mod_base::dsl::*;
        if self.uuid.is_nil() {
            self.uuid = Uuid::new_v4();
        }
        diesel::insert_into(mod_base)
            .values(self)
            .get_result(c)
    }

    pub fn update(&self, c: &PgConnection) -> QueryResult<()> {
        use super::schema::mod_base::dsl::*;
        diesel::update(mod_base)
            .set(self)
            .execute(c)?;
        Ok(())
    }

    pub fn retrieve_mod_versions(&self, c: &PgConnection) -> QueryResult<Vec<ModVersion>> {
        use super::schema::mod_version::dsl::*;
        mod_version
            .filter(mod_base.eq(self.uuid))
            .load(c)
    }

    pub fn retrieve_display_version(&self, c: &PgConnection) -> QueryResult<Option<ModVersion>> {
        match &self.display_version {
            None => Ok(None),
            Some(s) => Ok(Some(ModVersion::by_primary_key(&self.uuid, &s, c)?))
        }
    }
}

impl ModVersion {
    pub fn by_primary_key(k_mod_base: &Uuid, k_file_version: &str, c: &PgConnection) -> QueryResult<ModVersion> {
        use super::schema::mod_version::dsl::*;
        mod_version
            .filter(mod_base.eq(k_mod_base))
            .filter(file_version.eq(k_file_version))
            .first(c)
    }

    pub fn insert(self, c: &PgConnection) -> QueryResult<Self> {
        use super::schema::mod_version::dsl::*;
        diesel::insert_into(mod_version)
            .values(self)
            .get_result(c)
    }

    pub fn update(&self, c: &PgConnection) -> QueryResult<()> {
        use super::schema::mod_version::dsl::*;
        diesel::update(mod_version)
            .set(self)
            .execute(c)?;
        Ok(())
    }

    pub fn retrieve_mod_base(&self, c: &PgConnection) -> QueryResult<ModBase> {
        use super::schema::mod_base::dsl::*;
        mod_base
            .filter(uuid.eq(self.mod_base))
            .get_result(c)
    }
}
\ No newline at end of file

D site/src/db/schema.patch => site/src/db/schema.patch +0 -48
@@ 1,48 0,0 @@
diff --git a/src/db/schema.rs b/src/db/schema.rs
index d68ceea..ea105f3 100644
--- a/src/db/schema.rs
+++ b/src/db/schema.rs
@@ -64,25 +64,25 @@ table! {
 table! {
     use diesel::sql_types::*;
     use crate::db::types::*;
 
     mod_link (mod_base, link_type, child) {
         mod_base -> Uuid,
-        link_type -> Link_type,
+        link_type -> LinkTypeT,
         child -> Uuid,
         range -> Varchar,
     }
 }
 
 table! {
     use diesel::sql_types::*;
     use crate::db::types::*;
 
     mod_link_external (mod_base, link_type, child) {
         mod_base -> Uuid,
-        link_type -> Link_type,
+        link_type -> LinkTypeT,
         child -> Varchar,
         range -> Varchar,
     }
 }
 
 table! {
@@ -96,13 +96,13 @@ table! {
         description -> Nullable<Varchar>,
         homepage -> Nullable<Varchar>,
         issues -> Nullable<Varchar>,
         source -> Nullable<Varchar>,
         license -> Nullable<Varchar>,
         download_link -> Varchar,
-        environment -> Environment,
+        environment -> EnvironmentT,
         file_version -> Varchar,
     }
 }
 
 joinable!(category_assoc -> category (category));
 joinable!(category_assoc -> mod_base (mod_base));
 joinable!(display_version -> mod_base (mod_base));

D site/src/db/schema.rs => site/src/db/schema.rs +0 -114
@@ 1,114 0,0 @@
table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    category (uuid) {
        uuid -> Uuid,
        name -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    category_assoc (mod_base, category) {
        mod_base -> Uuid,
        category -> Uuid,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    github_source (mod_base) {
        mod_base -> Uuid,
        username -> Varchar,
        repo -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    maven_source (mod_base) {
        mod_base -> Uuid,
        repo -> Varchar,
        group -> Varchar,
        artifact -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_base (uuid) {
        uuid -> Uuid,
        id -> Varchar,
        display_version -> Nullable<Varchar>,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_link (mod_base, link_type, child) {
        mod_base -> Uuid,
        link_type -> LinkTypeT,
        child -> Uuid,
        range -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_link_external (mod_base, link_type, child) {
        mod_base -> Uuid,
        link_type -> LinkTypeT,
        child -> Varchar,
        range -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_version (mod_base, file_version) {
        mod_base -> Uuid,
        id -> Varchar,
        version -> Varchar,
        name -> Nullable<Varchar>,
        description -> Nullable<Varchar>,
        homepage -> Nullable<Varchar>,
        issues -> Nullable<Varchar>,
        source -> Nullable<Varchar>,
        license -> Nullable<Varchar>,
        download_link -> Varchar,
        environment -> EnvironmentT,
        file_version -> Varchar,
    }
}

joinable!(category_assoc -> category (category));
joinable!(category_assoc -> mod_base (mod_base));
joinable!(github_source -> mod_base (mod_base));
joinable!(maven_source -> mod_base (mod_base));
joinable!(mod_link_external -> mod_base (mod_base));

allow_tables_to_appear_in_same_query!(
    category,
    category_assoc,
    github_source,
    maven_source,
    mod_base,
    mod_link,
    mod_link_external,
    mod_version,
);

D site/src/db/schema.rs.orig => site/src/db/schema.rs.orig +0 -114
@@ 1,114 0,0 @@
table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    category (uuid) {
        uuid -> Uuid,
        name -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    category_assoc (mod_base, category) {
        mod_base -> Uuid,
        category -> Uuid,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    github_source (mod_base) {
        mod_base -> Uuid,
        username -> Varchar,
        repo -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    maven_source (mod_base) {
        mod_base -> Uuid,
        repo -> Varchar,
        group -> Varchar,
        artifact -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_base (uuid) {
        uuid -> Uuid,
        id -> Varchar,
        display_version -> Nullable<Varchar>,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_link (mod_base, link_type, child) {
        mod_base -> Uuid,
        link_type -> Link_type,
        child -> Uuid,
        range -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_link_external (mod_base, link_type, child) {
        mod_base -> Uuid,
        link_type -> Link_type,
        child -> Varchar,
        range -> Varchar,
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::types::*;

    mod_version (mod_base, file_version) {
        mod_base -> Uuid,
        id -> Varchar,
        version -> Varchar,
        name -> Nullable<Varchar>,
        description -> Nullable<Varchar>,
        homepage -> Nullable<Varchar>,
        issues -> Nullable<Varchar>,
        source -> Nullable<Varchar>,
        license -> Nullable<Varchar>,
        download_link -> Varchar,
        environment -> Environment,
        file_version -> Varchar,
    }
}

joinable!(category_assoc -> category (category));
joinable!(category_assoc -> mod_base (mod_base));
joinable!(github_source -> mod_base (mod_base));
joinable!(maven_source -> mod_base (mod_base));
joinable!(mod_link_external -> mod_base (mod_base));

allow_tables_to_appear_in_same_query!(
    category,
    category_assoc,
    github_source,
    maven_source,
    mod_base,
    mod_link,
    mod_link_external,
    mod_version,
);

M site/src/db/types.rs => site/src/db/types.rs +8 -66
@@ 1,17 1,9 @@
use std::io::Write;

use diesel::{deserialize, serialize};
use diesel::deserialize::FromSql;
use diesel::pg::Pg;
use diesel::serialize::{IsNull, Output, ToSql};
use serde::Serialize;
use sqlx::Type;

#[derive(SqlType)]
#[postgres(type_name = "link_type")]
pub struct LinkTypeT;

#[derive(Debug, Clone, Copy, Eq, PartialEq, FromSqlRow, AsExpression)]
#[sql_type = "LinkTypeT"]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Type)]
#[sqlx(rename = "LINK_TYPE")]
#[sqlx(rename_all = "lowercase")]
pub enum LinkType {
    Depends,
    Recommends,


@@ 20,62 12,12 @@ pub enum LinkType {
    Breaks,
}

#[derive(SqlType)]
#[postgres(type_name = "environment")]
pub struct EnvironmentT;

#[derive(Debug, Clone, Copy, Eq, PartialEq, FromSqlRow, AsExpression, Serialize)]
#[sql_type = "EnvironmentT"]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Type)]
#[sqlx(rename = "ENVIRONMENT")]
#[sqlx(rename_all = "lowercase")]
pub enum Environment {
    #[sqlx(rename = "*")]
    Any,
    Client,
    Server,
}

impl ToSql<LinkTypeT, Pg> for LinkType {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
        match *self {
            LinkType::Depends => out.write_all(b"depends")?,
            LinkType::Recommends => out.write_all(b"recommends")?,
            LinkType::Suggests => out.write_all(b"suggests")?,
            LinkType::Conflicts => out.write_all(b"conflicts")?,
            LinkType::Breaks => out.write_all(b"breaks")?,
        }
        Ok(IsNull::No)
    }
}

impl FromSql<LinkTypeT, Pg> for LinkType {
    fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
        match not_none!(bytes) {
            b"depends" => Ok(LinkType::Depends),
            b"recommends" => Ok(LinkType::Recommends),
            b"suggests" => Ok(LinkType::Suggests),
            b"conflicts" => Ok(LinkType::Conflicts),
            b"breaks" => Ok(LinkType::Breaks),
            _ => Err("Unrecognized enum variant".into()),
        }
    }
}

impl ToSql<EnvironmentT, Pg> for Environment {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
        match *self {
            Environment::Any => out.write_all(b"*")?,
            Environment::Client => out.write_all(b"client")?,
            Environment::Server => out.write_all(b"server")?,
        }
        Ok(IsNull::No)
    }
}

impl FromSql<EnvironmentT, Pg> for Environment {
    fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
        match not_none!(bytes) {
            b"*" => Ok(Environment::Any),
            b"client" => Ok(Environment::Client),
            b"server" => Ok(Environment::Server),
            _ => Err("Unrecognized enum variant".into()),
        }
    }
}
\ No newline at end of file

M site/src/indexer/db.rs => site/src/indexer/db.rs +2 -1
@@ 1,8 1,9 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use crate::fabric_mod::Environment;

use crate::db;
use crate::fabric_mod::Environment;

#[derive(Serialize, Deserialize)]
pub struct ModInfo {

M site/src/indexer/github.rs => site/src/indexer/github.rs +3 -3
@@ 1,13 1,13 @@
use reqwest::Client;
use serde::Deserialize;

use crate::modconf::modsrc::GithubSource;
use crate::indexer::{ModVersion, ModVersions};
use crate::modconf::modsrc::GithubSource;

pub async fn get_versions(source: &GithubSource, client: &Client) -> anyhow::Result<ModVersions> {
    let response: Vec<Release> = client.get(&format!("https://api.github.com/repos/{}/{}/releases", source.username, source.repo)).send().await?.json().await?;

    let versions:Vec<ModVersion> = response.into_iter().filter_map(|r| {
    let versions: Vec<ModVersion> = response.into_iter().filter_map(|r| {
        let candidates: Vec<_> = r.assets.into_iter().map(|a| a.browser_download_url)
            .filter(|s| s.ends_with(".jar"))
            .filter(|s| !s.ends_with("-sources.jar"))


@@ 37,7 37,7 @@ pub async fn get_versions(source: &GithubSource, client: &Client) -> anyhow::Res

    Ok(ModVersions {
        latest: versions.first().map(|s| s.version.clone()),
        versions
        versions,
    })
}


M site/src/indexer/maven.rs => site/src/indexer/maven.rs +5 -4
@@ 1,9 1,10 @@
use reqwest::Client;
use serde::Deserialize;

use crate::indexer::{ModVersion, ModVersions};
use crate::modconf::modsrc::MavenSource;
use reqwest::Client;
use crate::indexer::{ModVersions, ModVersion};

pub async fn get_versions(source: &MavenSource, client:&Client) -> anyhow::Result<ModVersions> {
pub async fn get_versions(source: &MavenSource, client: &Client) -> anyhow::Result<ModVersions> {
    let url = source.url();
    let r = client.get(&format!("{}/maven-metadata.xml", url)).send().await?;
    let t = r.text().await?;


@@ 16,7 17,7 @@ pub async fn get_versions(source: &MavenSource, client:&Client) -> anyhow::Resul
                version: s,
                file_url,
            }
        }).collect()
        }).collect(),
    })
}


M site/src/main.rs => site/src/main.rs +35 -56
@@ 1,21 1,19 @@
#![feature(decl_macro, proc_macro_hygiene)]

#[macro_use]
extern crate diesel;
#![feature(decl_macro, proc_macro_hygiene, trait_alias)]

use std::collections::HashMap;
use std::fs::File;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;

use diesel::prelude::*;
use diesel::r2d2::ConnectionManager;
use log::info;
use log::LevelFilter;
use simplelog::{CombinedLogger, Config, ConfigBuilder, TerminalMode, TermLogger, WriteLogger};
use sqlx::{Executor, PgConnection, PgPool, Pool, Postgres};
use uuid::Uuid;

use crate::db::models::{ModBase, ModVersion};
use crate::db::types::Environment;
use crate::indexer::db::ModInfo;
use crate::modconf::UserFiles;



@@ 29,6 27,8 @@ mod db;
const CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
const GIT_HASH: &str = env!("GIT_HASH");

trait PgExec = Executor<Database=Postgres>;

#[actix_rt::main]
async fn main() {
    let config = load_config();


@@ 45,17 45,16 @@ async fn main() {
    info!("Using database {}", config.db_url);
    info!("Using max {}, min {} database connections", config.db_pool_size, config.db_pool_size_min);

    let manager = ConnectionManager::<PgConnection>::new(&config.db_url);
    let pool = r2d2::Pool::builder()
    let pool = PgPool::builder()
        .max_size(config.db_pool_size)
        .min_idle(Some(config.db_pool_size_min))
        .build(manager)
        .unwrap();
        .min_size(config.db_pool_size_min)
        .idle_timeout(Some(Duration::from_secs(600)))
        .build(&config.db_url).await.unwrap();

    if config.do_scan {
        let user_files = UserFiles::load(&config.source_dir).unwrap();
        let modinfo = indexer::index_all(user_files.mods()).await;
        put_into_db(modinfo, &pool.get().unwrap());
        put_into_db(modinfo, &pool).await.unwrap();
    }

    if !config.addrs.is_empty() {


@@ 118,49 117,29 @@ fn load_config() -> LaunchConfig {
    }
}

fn put_into_db(modinfo: HashMap<String, ModInfo>, db: &PgConnection) {
    db.transaction::<_, diesel::result::Error, _>(|| {
        {
            use crate::db::schema::*;
            diesel::update(mod_base::table).set(mod_base::display_version.eq(None::<&str>)).execute(db).unwrap();
            diesel::delete(mod_version::table).execute(db).unwrap();
            diesel::delete(mod_base::table).execute(db).unwrap();
        }
async fn put_into_db(modinfo: HashMap<String, ModInfo>, db: &Pool<PgConnection>) -> anyhow::Result<()> {
    let mut ta = db.begin().await?;

    sqlx::query!("UPDATE mod_base SET display_version = NULL").execute(&mut ta).await?;
    sqlx::query!("DELETE FROM mod_version").execute(&mut ta).await?;
    sqlx::query!("DELETE FROM mod_base").execute(&mut ta).await?;

        for (_, mod_info) in modinfo {
            let mod_base = {
                let mod_base = ModBase {
                    uuid: Default::default(),
                    id: mod_info.id.clone(),
                    display_version: None,
                };
                mod_base.insert(db)?
            };
            for (file_version, vi) in mod_info.versions {
                let mod_version = ModVersion {
                    mod_base: mod_base.uuid,
                    version: vi.version,
                    id: vi.modid,
                    name: vi.name,
                    description: vi.description,
                    homepage: vi.homepage,
                    issues: vi.issues,
                    source: vi.source,
                    license: vi.license,
                    download_link: vi.file_url,
                    environment: vi.environment.into(),
                    file_version,
                };
                mod_version.insert(db)?;
            }
            {
                use diesel::prelude::*;
                use crate::db::schema::mod_base as mb;
                diesel::update(mb::table.filter(mb::uuid.eq(&mod_base.uuid)))
                    .set(mb::display_version.eq(&mod_info.display_version))
                    .execute(db)?;
            }
    for (_, mod_info) in modinfo {
        let mod_base = Uuid::new_v4();
        sqlx::query!("INSERT INTO mod_base (uuid, id, display_version) VALUES ($1, $2, $3)", mod_base, mod_info.id, None::<&str>).execute(&mut ta).await?;

        for (file_version, vi) in mod_info.versions {
            let e: Environment = vi.environment.into();
            sqlx::query_unchecked!(
                "INSERT INTO mod_version (mod_base, version, id, name, description, homepage, issues, source, license, download_link, environment, file_version) \
                 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
                mod_base, vi.version, vi.modid, vi.name, vi.description, vi.homepage, vi.issues, vi.source, vi.license, vi.file_url, e, file_version)
                .execute(&mut ta).await?;
        }
        Ok(())
    }).unwrap();

        sqlx::query!("UPDATE mod_base SET display_version = $1 WHERE uuid = $2", mod_info.display_version, mod_base)
            .execute(&mut ta).await?;
    }
    ta.commit().await?;
    Ok(())
}
\ No newline at end of file

M site/src/modconf/modsrc.rs => site/src/modconf/modsrc.rs +1 -1
@@ 114,7 114,7 @@ impl ModSource {
                    eprintln!("warning: Mod {} has both maven and GitHub sources defined! Picking GitHub", id);
                    FileSource::Github(gs)
                }
                (None,None) => {
                (None, None) => {
                    eprintln!("error: Mod {} has no sources defined! Skipping mod", id);
                    break;
                }

M site/src/site/db_access.rs => site/src/site/db_access.rs +15 -14
@@ 3,13 3,17 @@ use std::ops::{Deref, DerefMut};
use actix_web::{FromRequest, HttpRequest};
use actix_web::dev::{Payload, PayloadStream};
use actix_web::error::{ErrorInternalServerError, ErrorNotFound, ErrorServiceUnavailable};
use diesel::PgConnection;
use diesel::r2d2::ConnectionManager;
use futures_util::future::{err, ok, Ready};
use sqlx::{PgConnection, Pool};

pub struct Db(r2d2::PooledConnection<ConnectionManager<PgConnection>>);
#[derive(Clone)]
pub struct Db(Pool<PgConnection>);

pub struct DbPool(pub r2d2::Pool<ConnectionManager<PgConnection>>);
impl Db {
    pub fn new(pool: Pool<PgConnection>) -> Self {
        Db(pool)
    }
}

impl FromRequest for Db {
    type Error = actix_web::Error;


@@ 18,11 22,8 @@ impl FromRequest for Db {

    // see Data<T>
    fn from_request(req: &HttpRequest, payload: &mut Payload<PayloadStream>) -> Self::Future {
        match req.app_data::<DbPool>() {
            Some(pool) => match pool.0.get() {
                Ok(conn) => ok(Db(conn)),
                Err(_) => err(ErrorServiceUnavailable("Database not accessible"))
            },
        match req.app_data::<Db>() {
            Some(pool) => ok(pool.clone()),
            None => {
                err(ErrorInternalServerError("Database not connected"))
            }


@@ 31,22 32,22 @@ impl FromRequest for Db {
}

impl Deref for Db {
    type Target = PgConnection;
    type Target = Pool<PgConnection>;

    fn deref(&self) -> &Self::Target {
        &*self.0
        &self.0
    }
}

impl DerefMut for Db {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut *self.0
        &mut self.0
    }
}

pub fn map_db_err(err: diesel::result::Error) -> actix_web::Error {
pub fn map_db_err(err: sqlx::Error) -> actix_web::Error {
    match err {
        e @ diesel::result::Error::NotFound => ErrorNotFound(e),
        e @ sqlx::Error::RowNotFound => ErrorNotFound(e),
        e => ErrorInternalServerError(e)
    }
}
\ No newline at end of file

M site/src/site/main_page.rs => site/src/site/main_page.rs +13 -40
@@ 1,7 1,8 @@
use actix_web::{HttpRequest, HttpResponse, Responder, web};
use diesel::prelude::*;
use futures_util::TryStreamExt;
use handlebars::Handlebars;
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgConnection, Pool};

use crate::db::types::Environment;
use crate::site::db_access::{Db, map_db_err};


@@ 12,7 13,7 @@ pub async fn main_page(req: HttpRequest, page_state: web::Query<PageState>, hb: 

    let body = hb.render("index", &Data {
        current_query: page_state.query.as_deref(),
        mods: ModData::get_all(&db).map_err(map_db_err)?,
        mods: ModData::get_all(&db).await.map_err(map_db_err)?,
        geninfo: GenInfo::current(),
        sorting,
    }).unwrap();


@@ 49,16 50,6 @@ enum SortType {
    Updated,
}

impl SortType {
    pub fn as_str(&self) -> &str {
        match self {
            SortType::Name => "name",
            SortType::Created => "created",
            SortType::Updated => "updated",
        }
    }
}

impl Default for SortType {
    fn default() -> Self { SortType::Updated }
}


@@ 72,14 63,7 @@ enum SortOrder {
}

impl SortOrder {
    fn as_str(&self) -> &str {
        match self {
            SortOrder::Asc => "asc",
            SortOrder::Desc => "desc",
        }
    }

    fn next(&self) -> SortOrder {
    fn next(self) -> SortOrder {
        match self {
            SortOrder::Asc => SortOrder::Desc,
            SortOrder::Desc => SortOrder::Asc,


@@ 105,7 89,7 @@ struct ModData {
    from_db: FromDb,
}

#[derive(Queryable, Serialize)]
#[derive(FromRow, Serialize)]
struct FromDb {
    id: String,
    modid: String,


@@ 120,25 104,14 @@ struct FromDb {
}

impl ModData {
    fn get_all(db: &PgConnection) -> QueryResult<Vec<ModData>> {
        use crate::db::schema::mod_base;
        use crate::db::schema::mod_version;
        let from_db: Vec<FromDb> = mod_base::table
            .inner_join(mod_version::table.on(
                mod_version::file_version.nullable().eq(mod_base::display_version)
                    .and(mod_version::mod_base.eq(mod_base::uuid))
            ))
            .select((mod_base::id,
                     mod_version::id,
                     mod_version::name,
                     mod_version::description,
                     mod_version::homepage,
                     mod_version::issues,
                     mod_version::source,
                     mod_version::license,
                     mod_version::download_link,
                     mod_version::environment))
            .load(db)?;
    async fn get_all(db: &Pool<PgConnection>) -> sqlx::Result<Vec<ModData>> {
        let from_db: Vec<FromDb> = sqlx::query_as_unchecked!(FromDb,
                "SELECT b.id, v.id as modid, v.name, v.description, v.homepage, v.issues, v.source, v.license, v.download_link, v.environment \
                 FROM mod_base b \
                 INNER JOIN mod_version v ON (v.file_version = b.display_version AND v.mod_base = b.uuid)")
            .fetch(db)
            .try_collect().await?;

        Ok(from_db.into_iter()
            .map(|data| ModData {
                display_name: data.name.clone().unwrap_or_else(|| data.id.clone()),

M site/src/site/mod.rs => site/src/site/mod.rs +4 -5
@@ 1,14 1,13 @@
use std::io;

use actix_web::{App, guard, HttpServer, web};
use diesel::PgConnection;
use diesel::r2d2::ConnectionManager;
use handlebars::Handlebars;
use itertools::Itertools;
use log::info;
use sqlx::{PgConnection, Pool};

use crate::LaunchConfig;
use crate::site::db_access::DbPool;
use crate::site::db_access::Db;

mod geninfo;
mod main_page;


@@ 16,7 15,7 @@ mod mod_page;

mod db_access;

pub async fn start_site(config: &LaunchConfig, db_pool: r2d2::Pool<ConnectionManager<PgConnection>>) -> io::Result<()> {
pub async fn start_site(config: &LaunchConfig, db_pool: Pool<PgConnection>) -> io::Result<()> {
    info!("Using {} http workers", config.http_threads);
    info!("Listening on {}", config.addrs.iter().map(|a| format!("http://{}", a)).join(", "));



@@ 32,7 31,7 @@ pub async fn start_site(config: &LaunchConfig, db_pool: r2d2::Pool<ConnectionMan
            .service(web::resource("/").name("main_page").guard(guard::Get()).to(main_page::main_page))
            .service(web::resource("/mod").name("mod_page").guard(guard::Get()).to(mod_page::mod_page))
            .service(actix_files::Files::new("/static", "static"))
            .app_data(DbPool(db_pool.clone()))
            .app_data(Db::new(db_pool.clone()))
            .app_data(hb.clone())
    })
        .workers(config.http_threads)

M site/src/site/mod_page.rs => site/src/site/mod_page.rs +27 -61
@@ 1,7 1,8 @@
use actix_web::{HttpRequest, HttpResponse, Responder, web};
use diesel::prelude::*;
use futures_util::TryStreamExt;
use handlebars::Handlebars;
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgConnection, Pool};

use crate::db::types::Environment;
use crate::site::db_access::{Db, map_db_err};


@@ 9,11 10,11 @@ use crate::site::geninfo::GenInfo;

pub async fn mod_page(_req: HttpRequest, web::Query(PageState { id, v: version }): web::Query<PageState>, hb: web::Data<Handlebars<'_>>, db: Db) -> Result<impl Responder, actix_web::Error> {
    let from_db = if let Some(version) = version {
        FromDb::load_ver(&id, &version, &db).map_err(map_db_err)?
        FromDb::load_ver(&id, &version, &db).await.map_err(map_db_err)?
    } else {
        FromDb::load(&id, &db).map_err(map_db_err)?
        FromDb::load(&id, &db).await.map_err(map_db_err)?
    };
    let all_versions = ModVersion::load_all(&id, &from_db.file_version, &db).map_err(map_db_err)?;
    let all_versions = ModVersion::load_all(&id, &from_db.file_version, &db).await.map_err(map_db_err)?;

    let body = hb.render("mod", &Data {
        id,


@@ 58,22 59,13 @@ struct ModVersion {
}

impl ModVersion {
    fn load_all(id: &str, current: &str, db: &PgConnection) -> QueryResult<Vec<ModVersion>> {
        use diesel::prelude::*;
        use crate::db::schema::{mod_base, mod_version};

        #[derive(Queryable)]
        struct ModVersionFromDb {
            download_link: String,
            file_version: String,
        }

        let db: Vec<ModVersionFromDb> = mod_version::table
            .left_join(mod_base::table.on(mod_base::uuid.eq(mod_version::mod_base)))
            .filter(mod_base::id.eq(id))
            .select((mod_version::download_link,
                     mod_version::file_version))
            .load(db)?;
    async fn load_all(id: &str, current: &str, db: &Pool<PgConnection>) -> sqlx::Result<Vec<ModVersion>> {
        let db: Vec<_> = sqlx::query!(
                "SELECT v.download_link, v.file_version FROM mod_version v \
                 JOIN mod_base b ON b.uuid = v.mod_base \
                 WHERE b.id = $1", id)
            .fetch(db)
            .try_collect().await?;

        Ok(db.into_iter().map(|db| ModVersion {
            current: db.file_version == current,


@@ 84,7 76,7 @@ impl ModVersion {
    }
}

#[derive(Queryable, Serialize)]
#[derive(FromRow, Serialize)]
struct FromDb {
    modid: String,
    name: Option<String>,


@@ 99,47 91,21 @@ struct FromDb {
}

impl FromDb {
    fn load(id: &str, db: &PgConnection) -> QueryResult<FromDb> {
        use diesel::prelude::*;
        use crate::db::schema::{mod_base, mod_version};
        mod_base::table
            .filter(mod_base::id.eq(id))
            .inner_join(mod_version::table.on(
                mod_version::file_version.nullable().eq(mod_base::display_version)
                    .and(mod_version::mod_base.eq(mod_base::uuid))
            ))
            .select((mod_version::id,
                     mod_version::name,
                     mod_version::file_version,
                     mod_version::description,
                     mod_version::homepage,
                     mod_version::issues,
                     mod_version::source,
                     mod_version::license,
                     mod_version::download_link,
                     mod_version::environment))
            .get_result(db)
    async fn load(id: &str, db: &Pool<PgConnection>) -> sqlx::Result<FromDb> {
        sqlx::query_as_unchecked!(FromDb,
            "SELECT v.id as modid, v.name, v.file_version, v.description, v.homepage, v.issues, v.source, v.license, v.download_link, v.environment \
             FROM mod_version v \
             INNER JOIN mod_base b ON b.uuid = v.mod_base \
             WHERE b.id = $1 AND v.file_version = b.display_version", id)
            .fetch_one(db).await
    }

    fn load_ver(id: &str, v: &str, db: &PgConnection) -> QueryResult<FromDb> {
        use diesel::prelude::*;
        use crate::db::schema::{mod_base, mod_version};
        mod_version::table
            .filter(mod_version::file_version.eq(v))
            .inner_join(mod_base::table.on(
                mod_version::mod_base.eq(mod_base::uuid)
                    .and(mod_base::id.eq(id))
            ))
            .select((mod_version::id,
                     mod_version::name,
                     mod_version::file_version,
                     mod_version::description,
                     mod_version::homepage,
                     mod_version::issues,
                     mod_version::source,
                     mod_version::license,
                     mod_version::download_link,
                     mod_version::environment))
            .get_result(db)
    async fn load_ver(id: &str, v: &str, db: &Pool<PgConnection>) -> sqlx::Result<FromDb> {
        sqlx::query_as_unchecked!(FromDb,
            "SELECT v.id as modid, v.name, v.file_version, v.description, v.homepage, v.issues, v.source, v.license, v.download_link, v.environment \
             FROM mod_version v \
             INNER JOIN mod_base b ON b.uuid = v.mod_base \
             WHERE b.id = $1 AND v.file_version = $2", id, v)
            .fetch_one(db).await
    }
}
\ No newline at end of file

M srvrc => srvrc +1 -1
@@ 11,4 11,4 @@ db_pool_size 1
db_pool_size_min 1

source_dir data/
//scan
\ No newline at end of file
// scan
\ No newline at end of file