feat: add extras section, powered by mediawiki
This commit is contained in:
parent
896bfe1002
commit
50272298a1
|
@ -3,3 +3,4 @@ assets-gen/
|
|||
assetindex.json
|
||||
result/
|
||||
.direnv/
|
||||
.env
|
||||
|
|
|
@ -677,6 +677,33 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"idna 0.2.3",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
|
@ -1390,6 +1417,17 @@ dependencies = [
|
|||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
|
@ -2443,6 +2481,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psl-types"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta"
|
||||
version = "0.1.4"
|
||||
|
@ -2463,6 +2507,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457"
|
||||
dependencies = [
|
||||
"idna 0.3.0",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
|
@ -2726,6 +2780,8 @@ checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
|
|||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"cookie_store",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
|
@ -2741,6 +2797,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"proc-macro-hack",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
|
@ -3795,7 +3852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"idna 0.3.0",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ hex = "0.4"
|
|||
fastrand = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
time = { version = "0.3", features = ["formatting", "serde"] }
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false }
|
||||
reqwest = { version = "0.11", features = ["cookies", "json", "rustls-tls"], default-features = false }
|
||||
thiserror = "1"
|
||||
lazy_static = "1"
|
||||
image = { version = "0.24", default-features = false, features = ["png"]}
|
||||
|
|
|
@ -16,6 +16,7 @@ pub const NOWPLAYING_URL: &str = "https://api.ashhhleyyy.dev/playing";
|
|||
const MIN_REFRESH_TIME: Duration = Duration::from_secs(5);
|
||||
|
||||
pub(crate) mod fedi;
|
||||
pub(crate) mod mediawiki;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub(crate) static ref CLIENT: Client = ClientBuilder::new()
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
use maud::PreEscaped;
|
||||
use reqwest::{ClientBuilder, Client};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::Result, apis::USER_AGENT};
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MediawikiClient<S: ClientState> {
|
||||
client: Client,
|
||||
domain: String,
|
||||
state: S,
|
||||
}
|
||||
|
||||
impl MediawikiClient<Credentials> {
|
||||
pub fn new(domain: String, username: String, password: String) -> Self {
|
||||
let client = ClientBuilder::new()
|
||||
.user_agent(USER_AGENT)
|
||||
.cookie_store(true)
|
||||
.build().expect("failed to build client");
|
||||
Self {
|
||||
state: Credentials { username, password },
|
||||
client,
|
||||
domain,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_login_token(&self) -> Result<String> {
|
||||
let url = format!("https://{domain}/w/api.php?action=query&meta=tokens&format=json&type=login", domain = self.domain);
|
||||
let res: QueryResponse = self.client.get(url)
|
||||
.send().await?
|
||||
.error_for_status()?
|
||||
.json().await?;
|
||||
|
||||
Ok(res.query.tokens.login_token)
|
||||
}
|
||||
|
||||
pub async fn log_in(self) -> Result<MediawikiClient<LoggedIn>> {
|
||||
let login_token = self.get_login_token().await?;
|
||||
let url = format!( "https://{domain}/w/api.php", domain = self.domain);
|
||||
let _res = self.client.post(url)
|
||||
.form(&LoginForm {
|
||||
action: "login",
|
||||
lgname: self.state.username,
|
||||
lgpassword: self.state.password,
|
||||
lgtoken: login_token,
|
||||
format: "json",
|
||||
})
|
||||
.send().await?
|
||||
.error_for_status()?;
|
||||
|
||||
Ok(MediawikiClient {
|
||||
client: self.client,
|
||||
domain: self.domain,
|
||||
state: LoggedIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MediawikiClient<LoggedIn> {
|
||||
pub async fn get_page(&self, page_title: String) -> Result<(String, PreEscaped<String>)> {
|
||||
let url = format!(
|
||||
"https://{domain}/w/api.php?action=parse&format=json&page={page_title}",
|
||||
domain = self.domain,
|
||||
);
|
||||
|
||||
let res: ParseResponse = self.client.get(url)
|
||||
.send().await?
|
||||
.error_for_status()?
|
||||
.json().await?;
|
||||
|
||||
Ok((res.parse.title, PreEscaped(res.parse.text.content)))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ClientState {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Credentials {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LoggedIn;
|
||||
|
||||
impl ClientState for Credentials {}
|
||||
impl ClientState for LoggedIn {}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct QueryResponse {
|
||||
query: TokensResponse,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TokensResponse {
|
||||
tokens: Tokens,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Tokens {
|
||||
#[serde(rename = "logintoken")]
|
||||
login_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LoginForm {
|
||||
action: &'static str,
|
||||
lgname: String,
|
||||
lgpassword: String,
|
||||
lgtoken: String,
|
||||
format: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ParseResponse {
|
||||
pub parse: Parse,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Parse {
|
||||
pub title: String,
|
||||
pub revid: i64,
|
||||
pub text: Text,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Text {
|
||||
#[serde(rename = "*")]
|
||||
pub content: String,
|
||||
}
|
20
src/main.rs
20
src/main.rs
|
@ -12,7 +12,7 @@ mod templates;
|
|||
use std::path::Path;
|
||||
|
||||
use apis::{
|
||||
CachingFetcher, NowPlayingInfo, PronounsPageProfile, NOWPLAYING_URL, PRONOUNS_PAGE_URL,
|
||||
CachingFetcher, NowPlayingInfo, PronounsPageProfile, NOWPLAYING_URL, PRONOUNS_PAGE_URL, mediawiki::MediawikiClient,
|
||||
};
|
||||
use axum::extract::Extension;
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -23,6 +23,12 @@ use reqwest::StatusCode;
|
|||
use tower_http::services::ServeDir;
|
||||
use tracing_subscriber::{prelude::*, util::SubscriberInitExt};
|
||||
|
||||
macro_rules! fetch_env {
|
||||
($name:expr) => {
|
||||
::std::env::var($name).expect(concat!("environment variable `", $name, "` must be set!"))
|
||||
};
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> error::Result<()> {
|
||||
tracing_subscriber::registry()
|
||||
|
@ -37,9 +43,19 @@ async fn main() -> error::Result<()> {
|
|||
let nowplaying_client =
|
||||
CachingFetcher::<NowPlayingInfo>::new(NOWPLAYING_URL.to_string()).await?;
|
||||
|
||||
let mediawiki_client = MediawikiClient::new(
|
||||
"wiki.ashhhleyyy.dev".to_owned(),
|
||||
fetch_env!("MW_USERNAME"),
|
||||
fetch_env!("MW_PASSWORD"),
|
||||
);
|
||||
|
||||
tracing::info!("Logging into mediawiki instance...");
|
||||
let mediawiki_client = mediawiki_client.log_in().await?;
|
||||
|
||||
let app = routes::build_router()
|
||||
.layer(Extension(pronouns_page_client))
|
||||
.layer(Extension(nowplaying_client));
|
||||
.layer(Extension(nowplaying_client))
|
||||
.layer(Extension(mediawiki_client));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let app = app.nest(
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
use axum::{extract::{Path, Extension}, response::IntoResponse};
|
||||
use maud::PreEscaped;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
use crate::{apis::mediawiki::{MediawikiClient, LoggedIn}, templates::{ExtraTemplate, HtmlTemplate}};
|
||||
|
||||
pub async fn page(
|
||||
Path(title): Path<String>,
|
||||
Extension(mediawiki_client): Extension<MediawikiClient<LoggedIn>>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let (title, PreEscaped(content)) = mediawiki_client.get_page(title)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to get page: {}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
Ok(HtmlTemplate::new(
|
||||
format!("/extras/{title}"),
|
||||
ExtraTemplate {
|
||||
title,
|
||||
content,
|
||||
}
|
||||
).into_response().await)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod assets;
|
||||
mod blog;
|
||||
mod extras;
|
||||
mod projects;
|
||||
|
||||
use axum::{
|
||||
|
@ -85,6 +86,7 @@ pub fn build_router() -> Router {
|
|||
.route("/blog/:post", get(blog::post))
|
||||
.route("/projects/", get(projects::index))
|
||||
.route("/projects/:year/:project", get(projects::project))
|
||||
.route("/extras/:title", get(extras::page))
|
||||
.route("/me", get(links))
|
||||
.route("/assets-gen/background.svg", get(background))
|
||||
.route("/assets-gen/image.js", get(image_script))
|
||||
|
|
|
@ -69,6 +69,13 @@ pub struct ProjectTemplate {
|
|||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "extra.html")]
|
||||
pub struct ExtraTemplate {
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "error.html")]
|
||||
pub struct ErrorTemplate {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Pages > {{ title }}{% endblock %}
|
||||
{% block description %}An extra page. I wonder what this is 👀{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="content">
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
{{ content|safe }}
|
||||
</main>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue