feat: add extras section, powered by mediawiki

This commit is contained in:
Ashhhleyyy 2023-03-09 00:36:42 +00:00
parent 896bfe1002
commit 50272298a1
Signed by: ash
GPG Key ID: 83B789081A0878FB
11 changed files with 258 additions and 5 deletions

3
.envrc
View File

@ -1 +1,2 @@
use flake
use flake
dotenv_if_exists

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ assets-gen/
assetindex.json
result/
.direnv/
.env

59
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"]}

View File

@ -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()

131
src/apis/mediawiki.rs Normal file
View File

@ -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,
}

View File

@ -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(

25
src/routes/extras.rs Normal file
View File

@ -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)
}

View File

@ -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))

View File

@ -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 {

12
templates/extra.html Normal file
View File

@ -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 %}