WIP
This commit is contained in:
parent
abdcc6cbc4
commit
310a62cb72
4 changed files with 233 additions and 47 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -429,6 +429,7 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"url",
|
||||||
"validator",
|
"validator",
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue