This commit is contained in:
naskya 2024-06-20 11:42:32 +09:00
parent abdcc6cbc4
commit 310a62cb72
Signed by: naskya
GPG key ID: 712D413B3A9FED5C
4 changed files with 233 additions and 47 deletions

1
Cargo.lock generated
View file

@ -429,6 +429,7 @@ dependencies = [
"thiserror", "thiserror",
"tokio", "tokio",
"toml", "toml",
"url",
"validator", "validator",
"yaml-rust", "yaml-rust",
] ]

View file

@ -13,6 +13,7 @@ serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] } sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }
tokio = { version = "1.38", features = ["full"] } tokio = { version = "1.38", features = ["full"] }
toml = "0.8" toml = "0.8"
url = "2.5"
validator = { version = "0.18", features = ["derive"] } validator = { version = "0.18", features = ["derive"] }
yaml-rust = "0.4" yaml-rust = "0.4"

View file

@ -2,8 +2,10 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use crate::config::{client, server};
use sqlx::{postgres::PgConnectOptions, ConnectOptions}; use sqlx::{postgres::PgConnectOptions, ConnectOptions};
use std::{collections::HashMap, env, fs, io::Read}; use std::{collections::HashMap, env, fs, io::Read};
use url::Url;
use yaml_rust::{Yaml, YamlLoader}; use yaml_rust::{Yaml, YamlLoader};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -12,6 +14,10 @@ pub(crate) enum Error {
ReadYaml(#[from] ReadYamlConfigError), ReadYaml(#[from] ReadYamlConfigError),
#[error("failed to read the meta table")] #[error("failed to read the meta table")]
ReadMeta(#[from] sqlx::Error), ReadMeta(#[from] sqlx::Error),
#[error("invalid config ({0})")]
InvalidConfig(&'static str),
#[error("failed to parse server URL")]
InvalidUrl(#[from] url::ParseError),
} }
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -20,7 +26,7 @@ pub(crate) enum ReadYamlConfigError {
ReadFile(#[from] std::io::Error), ReadFile(#[from] std::io::Error),
#[error(transparent)] #[error(transparent)]
Yaml(#[from] yaml_rust::ScanError), Yaml(#[from] yaml_rust::ScanError),
#[error("invalid config file ({0})")] #[error("invalid config ({0})")]
InvalidConfig(String), InvalidConfig(String),
} }
@ -56,7 +62,7 @@ fn read_default_yml() -> Result<HashMap<String, Yaml>, ReadYamlConfigError> {
for (key, val) in content { for (key, val) in content {
let Some(key) = key.as_str() else { let Some(key) = key.as_str() else {
return Err(ReadYamlConfigError::InvalidConfig(format!( return Err(ReadYamlConfigError::InvalidConfig(format!(
"non-string key found: {:?}", "non-string key: {:?}",
key key
))); )));
}; };
@ -66,7 +72,7 @@ fn read_default_yml() -> Result<HashMap<String, Yaml>, ReadYamlConfigError> {
Ok(res) Ok(res)
} }
#[derive(sqlx::FromRow, Debug)] #[derive(sqlx::FromRow)]
#[sqlx(rename_all = "camelCase")] #[sqlx(rename_all = "camelCase")]
struct Meta { struct Meta {
name: Option<String>, name: Option<String>,
@ -172,23 +178,199 @@ async fn read_meta_table(
Ok(meta) Ok(meta)
} }
pub(super) async fn run() -> Result<(), Error> { async fn read_old_config() -> Result<(HashMap<String, Yaml>, Meta), Error> {
let old_config = read_default_yml()?; let default_yml = read_default_yml()?;
let db = default_yml
for (k, v) in &old_config { .get("db")
println!("{}:\n {:?}", k, v); .ok_or(Error::InvalidConfig("`db` is missing"))?;
}
let meta = read_meta_table( let meta = read_meta_table(
old_config["db"]["host"].as_str().unwrap(), db["host"].as_str().unwrap(),
old_config["db"]["port"].as_i64().unwrap() as u16, db["port"].as_i64().unwrap() as u16,
old_config["db"]["user"].as_str().unwrap(), db["user"].as_str().unwrap(),
old_config["db"]["pass"].as_str().unwrap(), db["pass"].as_str().unwrap(),
old_config["db"]["db"].as_str().unwrap(), db["db"].as_str().unwrap(),
) )
.await?; .await?;
println!("Meta: {:#?}", meta); Ok((default_yml, meta))
}
async fn create_new_config(
default_yml: HashMap<String, Yaml>,
meta: Meta,
) -> Result<(server::Config, client::Config), Error> {
let db = default_yml
.get("db")
.map(|db| db.as_hash())
.flatten()
.ok_or(Error::InvalidConfig("`db` is missing"))?;
let redis = default_yml
.get("redis")
.map(|redis| redis.as_hash())
.flatten()
.ok_or(Error::InvalidConfig("`redis` is missing"))?;
let server_url = default_yml
.get("url")
.map(|url| url.as_str())
.flatten()
.ok_or(Error::InvalidConfig("`url` is missing"))?;
let parsed_server_url = Url::parse(server_url)?;
let protocol = parsed_server_url.scheme().to_owned();
if protocol != "https" && protocol != "http" {
return Err(Error::InvalidConfig(
"server URL must start with http:// or https://",
));
}
let hostname = parsed_server_url.host_str().ok_or(Error::InvalidConfig(
"hostname is missing in the server url",
))?;
let host = match parsed_server_url.port() {
Some(port) => format!("{}:{}", hostname, port),
None => hostname.to_owned(),
};
let repository_url = match meta.repository_url.as_ref() {
"https://codeberg.org/firefish/firefish"
| "https://git.joinfirefish.org/firefish/firefish" => {
"https://firefish.dev/firefish/firefish".to_owned()
}
url => url.to_owned(),
};
let mut server_config = server::Config {
info: Some(server::Info {
name: meta.name,
description: meta.description,
maintainer_name: meta.maintainer_name,
contact_info: meta.maintainer_email,
open_registrations: !meta.disable_registration,
repository_url: Some(repository_url),
}),
timelines: Some(server::Timelines {
local: !meta.disable_local_timeline,
global: !meta.disable_global_timeline,
recommended: !meta.disable_recommended_timeline,
guest: meta.enable_guest_timeline,
}),
network: server::Network {
protocol: match protocol.as_str() {
"http" => Some(server::HttpProtocol::Http),
_ => Some(server::HttpProtocol::Https),
},
host,
port: default_yml
.get("port")
.map(|v| v.as_i64())
.flatten()
.ok_or(Error::InvalidConfig("port"))? as u16,
},
database: server::Database {
host: db
.get(&Yaml::String("host".to_string()))
.map(|v| v.as_str())
.flatten()
.ok_or(Error::InvalidConfig("db.host"))?
.to_string(),
port: db
.get(&Yaml::String("port".to_string()))
.map(|v| v.as_i64())
.flatten()
.ok_or(Error::InvalidConfig("db.port"))? as u16,
user: db
.get(&Yaml::String("user".to_string()))
.map(|v| v.as_str())
.flatten()
.ok_or(Error::InvalidConfig("db.user"))?
.to_string(),
password: db
.get(&Yaml::String("pass".to_string()))
.map(|v| v.as_str())
.flatten()
.ok_or(Error::InvalidConfig("db.pass"))?
.to_string(),
name: db
.get(&Yaml::String("db".to_string()))
.map(|v| v.as_str())
.flatten()
.ok_or(Error::InvalidConfig("db.db"))?
.to_string(),
},
cache_server: server::CacheServer {
host: redis
.get(&Yaml::String("host".to_string()))
.map(|v| v.as_str())
.flatten()
.ok_or(Error::InvalidConfig("redis.host"))?
.to_string(),
port: redis
.get(&Yaml::String("port".to_string()))
.map(|v| v.as_i64())
.flatten()
.ok_or(Error::InvalidConfig("redis.port"))? as u16,
user: match redis.get(&Yaml::String("user".to_string())) {
Some(user) => Some(
user.as_str()
.ok_or(Error::InvalidConfig("redis.user"))?
.to_string(),
),
None => None,
},
password: match redis.get(&Yaml::String("pass".to_string())) {
Some(user) => Some(
user.as_str()
.ok_or(Error::InvalidConfig("redis.pass"))?
.to_string(),
),
None => None,
},
index: match redis.get(&Yaml::String("db".to_string())) {
Some(user) => Some(user.as_i64().ok_or(Error::InvalidConfig("redis.db"))? as u8),
None => None,
},
prefix: match redis.get(&Yaml::String("prefix".to_string())) {
Some(user) => Some(
user.as_str()
.ok_or(Error::InvalidConfig("redis.prefix"))?
.to_string(),
),
None => None,
},
},
id: None,
};
if let Some(id) = default_yml.get("cuid") {
let id = id.as_hash().ok_or(Error::InvalidConfig("cuid"))?;
server_config.id = Some(server::Id {
length: match id.get(&Yaml::String("length".to_string())) {
Some(length) => {
Some(length.as_i64().ok_or(Error::InvalidConfig("cuid.length"))? as u8)
}
None => None,
},
fingerprint: match id.get(&Yaml::String("fingerprint".to_string())) {
Some(length) => Some(
length
.as_str()
.ok_or(Error::InvalidConfig("cuid.fingerpring"))?
.to_string(),
),
None => None,
},
});
}
Ok((server_config, todo!()))
}
pub(super) async fn run() -> Result<(), Error> {
let (default_yml, meta) = read_old_config().await?;
let (server_config, client_config) = create_new_config(default_yml, meta).await?;
Ok(()) Ok(())
} }

View file

@ -10,45 +10,47 @@ use validator::Validate;
#[derive(Deserialize, Serialize, Validate, Debug)] #[derive(Deserialize, Serialize, Validate, Debug)]
pub struct Config { pub struct Config {
info: Option<Info>, pub info: Option<Info>,
timelines: Option<Timelines>, pub timelines: Option<Timelines>,
network: Network, pub network: Network,
database: Database, pub database: Database,
cache_server: CacheServer, pub cache_server: CacheServer,
id: Option<Id>, pub id: Option<Id>,
} }
#[derive(Deserialize, Serialize, Validate, Debug)] #[derive(Deserialize, Serialize, Validate, Debug)]
pub struct Info { pub struct Info {
/// Server name /// Server name
name: Option<String>, pub name: Option<String>,
/// Server description /// Server description
description: Option<String>, pub description: Option<String>,
/// Name/handle of the server maintainer /// Name/handle of the server maintainer
maintainer_name: Option<String>, pub maintainer_name: Option<String>,
/// Contact info (e.g., email) of the server maintainer /// Contact info (e.g., email) of the server maintainer
contact_info: Option<String>, pub contact_info: Option<String>,
/// Whether the server allows open self-registration /// Whether the server allows open self-registration
open_registrations: bool, pub open_registrations: bool,
/// Repository URL /// Repository URL
repository_url: Option<String>, pub repository_url: Option<String>,
} }
#[derive(Deserialize, Serialize, Validate, Debug)] #[derive(Deserialize, Serialize, Validate, Debug)]
pub struct Timelines { pub struct Timelines {
/// Whether to enable the local timeline /// Whether to enable the local timeline
local: bool, pub local: bool,
/// Whether to enable the global timeline /// Whether to enable the global timeline
global: bool, pub global: bool,
/// Whether to enable the recommended timeline
pub recommended: bool,
/// Whether to publish the local/global timelines to signed out users /// Whether to publish the local/global timelines to signed out users
guest: bool, pub guest: bool,
} }
#[derive(Deserialize, Serialize, Validate, Debug)] #[derive(Deserialize, Serialize, Validate, Debug)]
pub struct Network { pub struct Network {
protocol: Option<HttpProtocol>, pub protocol: Option<HttpProtocol>,
host: String, pub host: String,
port: u16, pub port: u16,
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
@ -59,26 +61,26 @@ pub enum HttpProtocol {
#[derive(Deserialize, Serialize, Validate, Debug)] #[derive(Deserialize, Serialize, Validate, Debug)]
pub struct Database { pub struct Database {
host: String, pub host: String,
port: u16, pub port: u16,
user: String, pub user: String,
password: String, pub password: String,
name: String, pub name: String,
} }
#[derive(Deserialize, Serialize, Validate, Debug)] #[derive(Deserialize, Serialize, Validate, Debug)]
pub struct CacheServer { pub struct CacheServer {
host: String, pub host: String,
port: u16, pub port: u16,
user: Option<String>, pub user: Option<String>,
password: Option<String>, pub password: Option<String>,
index: Option<String>, pub index: Option<u8>,
prefix: Option<String>, pub prefix: Option<String>,
} }
#[derive(Deserialize, Serialize, Validate, Debug)] #[derive(Deserialize, Serialize, Validate, Debug)]
pub struct Id { pub struct Id {
#[validate(range(min = 16, max = 24))] #[validate(range(min = 16, max = 24))]
length: Option<u8>, pub length: Option<u8>,
fingerprint: Option<String>, pub fingerprint: Option<String>,
} }