mirror of https://github.com/spikecodes/libreddit
Implement instance info endpoint (JSON, YAML, TXT)
This commit is contained in:
parent
0ff92cbfe3
commit
1cc3104498
|
@ -679,6 +679,7 @@ dependencies = [
|
||||||
"sealed_test",
|
"sealed_test",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
@ -774,6 +775,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
@ -1158,6 +1168,19 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
@ -1267,6 +1290,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
|
@ -1435,6 +1460,12 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|
|
@ -21,13 +21,14 @@ percent-encoding = "2.2.0"
|
||||||
route-recognizer = "0.3.1"
|
route-recognizer = "0.3.1"
|
||||||
serde_json = "1.0.87"
|
serde_json = "1.0.87"
|
||||||
tokio = { version = "1.21.2", features = ["full"] }
|
tokio = { version = "1.21.2", features = ["full"] }
|
||||||
time = "0.3.17"
|
time = { version = "0.3.17", features = ["local-offset"] }
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
||||||
libflate = "1.2.0"
|
libflate = "1.2.0"
|
||||||
brotli = { version = "3.3.4", features = ["std"] }
|
brotli = { version = "3.3.4", features = ["std"] }
|
||||||
toml = "0.5.9"
|
toml = "0.5.9"
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
|
serde_yaml = "0.9.16"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lipsum = "0.8.2"
|
lipsum = "0.8.2"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
use std::process::Command;
|
||||||
|
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}");
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{env::var, fs::read_to_string};
|
use std::{env::var, fs::read_to_string};
|
||||||
|
|
||||||
// Waiting for https://github.com/rust-lang/rust/issues/74465 to land, so we
|
// Waiting for https://github.com/rust-lang/rust/issues/74465 to land, so we
|
||||||
|
@ -6,44 +7,47 @@ use std::{env::var, fs::read_to_string};
|
||||||
//
|
//
|
||||||
// This is the local static that is initialized at runtime (technically at
|
// This is the local static that is initialized at runtime (technically at
|
||||||
// first request) and contains the instance settings.
|
// first request) and contains the instance settings.
|
||||||
static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
pub(crate) static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
||||||
|
|
||||||
/// Stores the configuration parsed from the environment variables and the
|
/// Stores the configuration parsed from the environment variables and the
|
||||||
/// config file. `Config::Default()` contains None for each setting.
|
/// config file. `Config::Default()` contains None for each setting.
|
||||||
#[derive(Default, serde::Deserialize)]
|
/// When adding more config settings, add it to `Config::load`,
|
||||||
|
/// `get_setting_from_config`, both below, as well as
|
||||||
|
/// instance_info::InstanceInfo.to_string(), README.md and app.json.
|
||||||
|
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(rename = "LIBREDDIT_SFW_ONLY")]
|
#[serde(rename = "LIBREDDIT_SFW_ONLY")]
|
||||||
sfw_only: Option<String>,
|
pub(crate) sfw_only: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_THEME")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_THEME")]
|
||||||
default_theme: Option<String>,
|
pub(crate) default_theme: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
|
||||||
default_front_page: Option<String>,
|
pub(crate) default_front_page: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_LAYOUT")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_LAYOUT")]
|
||||||
default_layout: Option<String>,
|
pub(crate) default_layout: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_WIDE")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_WIDE")]
|
||||||
default_wide: Option<String>,
|
pub(crate) default_wide: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_COMMENT_SORT")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_COMMENT_SORT")]
|
||||||
default_comment_sort: Option<String>,
|
pub(crate) default_comment_sort: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_POST_SORT")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_POST_SORT")]
|
||||||
default_post_sort: Option<String>,
|
pub(crate) default_post_sort: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_SHOW_NSFW")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_SHOW_NSFW")]
|
||||||
default_show_nsfw: Option<String>,
|
pub(crate) default_show_nsfw: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_BLUR_NSFW")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_BLUR_NSFW")]
|
||||||
default_blur_nsfw: Option<String>,
|
pub(crate) default_blur_nsfw: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_USE_HLS")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_USE_HLS")]
|
||||||
default_use_hls: Option<String>,
|
pub(crate) default_use_hls: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
||||||
default_hide_hls_notification: Option<String>,
|
pub(crate) default_hide_hls_notification: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
use hyper::{http::Error, Body, Request, Response};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{Config, CONFIG},
|
||||||
|
server::RequestExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the local static that is intialized at runtime (technically at
|
||||||
|
// the first request *to the instance-info endpoint) and contains the data
|
||||||
|
// retrieved from the instance-info endpoint.
|
||||||
|
pub(crate) static INSTANCE_INFO: Lazy<InstanceInfo> = Lazy::new(InstanceInfo::new);
|
||||||
|
|
||||||
|
/// Handles instance info endpoint
|
||||||
|
pub async fn instance_info(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
|
let extension = req.param("extension").unwrap_or("json".into());
|
||||||
|
let response = match extension.as_str() {
|
||||||
|
"yaml" => info_yaml(),
|
||||||
|
"txt" => info_txt(),
|
||||||
|
"json" | _ => info_json(),
|
||||||
|
};
|
||||||
|
response.map_err(|err| format!("{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_json() -> Result<Response<Body>, Error> {
|
||||||
|
let body = serde_json::to_string(&*INSTANCE_INFO).unwrap_or("Error serializing JSON.".into());
|
||||||
|
Response::builder().status(200).header("content-type", "application/json").body(body.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_yaml() -> Result<Response<Body>, Error> {
|
||||||
|
let body = serde_yaml::to_string(&*INSTANCE_INFO).unwrap_or("Error serializing YAML.".into());
|
||||||
|
// https://github.com/ietf-wg-httpapi/mediatypes/blob/main/draft-ietf-httpapi-yaml-mediatypes.md
|
||||||
|
Response::builder().status(200).header("content-type", "application/yaml").body(body.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_txt() -> Result<Response<Body>, Error> {
|
||||||
|
Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body((INSTANCE_INFO.to_string()).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub(crate) struct InstanceInfo {
|
||||||
|
crate_version: String,
|
||||||
|
git_commit: String,
|
||||||
|
deploy_date: String,
|
||||||
|
compile_mode: String,
|
||||||
|
deploy_unix_ts: i64,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstanceInfo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
crate_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
git_commit: env!("GIT_HASH").to_string(),
|
||||||
|
deploy_date: OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()).to_string(),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
compile_mode: "Debug".into(),
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
compile_mode: "Release".into(),
|
||||||
|
deploy_unix_ts: OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()).unix_timestamp(),
|
||||||
|
config: CONFIG.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ToString for InstanceInfo {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Crate version: {}\n
|
||||||
|
Git commit: {}\n
|
||||||
|
Deploy date: {}\n
|
||||||
|
Deploy timestamp: {}\n
|
||||||
|
Compile mode: {}\n
|
||||||
|
Config:\n
|
||||||
|
SFW only: {:?}\n
|
||||||
|
Default theme: {:?}\n
|
||||||
|
Default front page: {:?}\n
|
||||||
|
Default layout: {:?}\n
|
||||||
|
Default wide: {:?}\n
|
||||||
|
Default comment sort: {:?}\n
|
||||||
|
Default post sort: {:?}\n
|
||||||
|
Default show NSFW: {:?}\n
|
||||||
|
Default blur NSFW: {:?}\n
|
||||||
|
Default use HLS: {:?}\n
|
||||||
|
Default hide HLS notification: {:?}\n",
|
||||||
|
self.crate_version,
|
||||||
|
self.git_commit,
|
||||||
|
self.deploy_date,
|
||||||
|
self.deploy_unix_ts,
|
||||||
|
self.compile_mode,
|
||||||
|
self.config.sfw_only,
|
||||||
|
self.config.default_theme,
|
||||||
|
self.config.default_front_page,
|
||||||
|
self.config.default_layout,
|
||||||
|
self.config.default_wide,
|
||||||
|
self.config.default_comment_sort,
|
||||||
|
self.config.default_post_sort,
|
||||||
|
self.config.default_show_nsfw,
|
||||||
|
self.config.default_blur_nsfw,
|
||||||
|
self.config.default_use_hls,
|
||||||
|
self.config.default_hide_hls_notification
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
13
src/main.rs
13
src/main.rs
|
@ -5,6 +5,7 @@
|
||||||
// Reference local files
|
// Reference local files
|
||||||
mod config;
|
mod config;
|
||||||
mod duplicates;
|
mod duplicates;
|
||||||
|
mod instance_info;
|
||||||
mod post;
|
mod post;
|
||||||
mod search;
|
mod search;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
@ -20,6 +21,7 @@ use hyper::{header::HeaderValue, Body, Request, Response};
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
use client::{canonical_path, proxy};
|
use client::{canonical_path, proxy};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use server::RequestExt;
|
use server::RequestExt;
|
||||||
use utils::{error, redirect, ThemeAssets};
|
use utils::{error, redirect, ThemeAssets};
|
||||||
|
|
||||||
|
@ -156,6 +158,13 @@ async fn main() {
|
||||||
// Begin constructing a server
|
// Begin constructing a server
|
||||||
let mut app = server::Server::new();
|
let mut app = server::Server::new();
|
||||||
|
|
||||||
|
// Force evaluation of statics. In instance_info case, we need to evaluate
|
||||||
|
// the timestamp so deploy date is accurate - in config case, we need to
|
||||||
|
// evaluate the configuration to avoid paying penalty at first request.
|
||||||
|
|
||||||
|
Lazy::force(&config::CONFIG);
|
||||||
|
Lazy::force(&instance_info::INSTANCE_INFO);
|
||||||
|
|
||||||
// Define default headers (added to all responses)
|
// Define default headers (added to all responses)
|
||||||
app.default_headers = headers! {
|
app.default_headers = headers! {
|
||||||
"Referrer-Policy" => "no-referrer",
|
"Referrer-Policy" => "no-referrer",
|
||||||
|
@ -283,6 +292,10 @@ async fn main() {
|
||||||
// Handle about pages
|
// Handle about pages
|
||||||
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
||||||
|
|
||||||
|
// Instance info page
|
||||||
|
app.at("/instance-info").get(|r| instance_info::instance_info(r).boxed());
|
||||||
|
app.at("/instance-info.:extension").get(|r| instance_info::instance_info(r).boxed());
|
||||||
|
|
||||||
app.at("/:id").get(|req: Request<Body>| {
|
app.at("/:id").get(|req: Request<Body>| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
match req.param("id").as_deref() {
|
match req.param("id").as_deref() {
|
||||||
|
|
Loading…
Reference in New Issue