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",
"tokio",
"toml",
"url",
"validator",
"yaml-rust",
]

View file

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

View file

@ -2,8 +2,10 @@
#![allow(clippy::type_complexity)]
use crate::config::{client, server};
use sqlx::{postgres::PgConnectOptions, ConnectOptions};
use std::{collections::HashMap, env, fs, io::Read};
use url::Url;
use yaml_rust::{Yaml, YamlLoader};
#[derive(thiserror::Error, Debug)]
@ -12,6 +14,10 @@ pub(crate) enum Error {
ReadYaml(#[from] ReadYamlConfigError),
#[error("failed to read the meta table")]
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)]
@ -20,7 +26,7 @@ pub(crate) enum ReadYamlConfigError {
ReadFile(#[from] std::io::Error),
#[error(transparent)]
Yaml(#[from] yaml_rust::ScanError),
#[error("invalid config file ({0})")]
#[error("invalid config ({0})")]
InvalidConfig(String),
}
@ -56,7 +62,7 @@ fn read_default_yml() -> Result<HashMap<String, Yaml>, ReadYamlConfigError> {
for (key, val) in content {
let Some(key) = key.as_str() else {
return Err(ReadYamlConfigError::InvalidConfig(format!(
"non-string key found: {:?}",
"non-string key: {:?}",
key
)));
};
@ -66,7 +72,7 @@ fn read_default_yml() -> Result<HashMap<String, Yaml>, ReadYamlConfigError> {
Ok(res)
}
#[derive(sqlx::FromRow, Debug)]
#[derive(sqlx::FromRow)]
#[sqlx(rename_all = "camelCase")]
struct Meta {
name: Option<String>,
@ -172,23 +178,199 @@ async fn read_meta_table(
Ok(meta)
}
pub(super) async fn run() -> Result<(), Error> {
let old_config = read_default_yml()?;
for (k, v) in &old_config {
println!("{}:\n {:?}", k, v);
}
async fn read_old_config() -> Result<(HashMap<String, Yaml>, Meta), Error> {
let default_yml = read_default_yml()?;
let db = default_yml
.get("db")
.ok_or(Error::InvalidConfig("`db` is missing"))?;
let meta = read_meta_table(
old_config["db"]["host"].as_str().unwrap(),
old_config["db"]["port"].as_i64().unwrap() as u16,
old_config["db"]["user"].as_str().unwrap(),
old_config["db"]["pass"].as_str().unwrap(),
old_config["db"]["db"].as_str().unwrap(),
db["host"].as_str().unwrap(),
db["port"].as_i64().unwrap() as u16,
db["user"].as_str().unwrap(),
db["pass"].as_str().unwrap(),
db["db"].as_str().unwrap(),
)
.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(())
}

View file

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