1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
/*
This file is part of the Toolforge Rust tutorial
Copyright 2021 Kunal Mehta and contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#[macro_use]
extern crate rocket;
use anyhow::Result;
use mysql_async::prelude::*;
use rocket::http::Status;
use rocket_dyn_templates::Template;
use serde::Serialize;
/// The context needed to render the "index.html" template
#[derive(Serialize)]
struct IndexTemplate {
/// The title variable
title: String,
}
/// The context needed to render the "error.html" template
#[derive(Serialize)]
struct ErrorTemplate {
/// The error message
error: String,
}
/// Handle all GET requests for the "/" route. We return either a `Template`
/// instance, or a `Template` instance with a specific HTTP status code.
#[get("/")]
async fn index() -> Result<Template, (Status, Template)> {
match get_latest_edit().await {
// Got the title, render the "index" template
Ok(title) => Ok(Template::render("index", IndexTemplate { title })),
// Some error occurred when trying to query MySQL, render the "error"
// template with a HTTP 500 status code
Err(err) => Err((
Status::InternalServerError,
Template::render(
"error",
ErrorTemplate {
error: err.to_string(),
},
),
)),
}
}
/// Get the latest edit via the enwiki database replica
async fn get_latest_edit() -> Result<String> {
// Read from ~/replica.my.cnf and build a connection URL
let db_url = toolforge::connection_info!("enwiki_p", WEB)?.to_string();
// Create a new database connection pool. We intentionally do not use
// Rocket's connection pooling facilities as Toolforge policy requires that
// we do not hold connections open while not in use. So we open the
// connection when we need to make a query and then immediately close it.
let pool = mysql_async::Pool::new(db_url.as_str());
// Open a connection and make a query
let mut conn = pool.get_conn().await?;
let resp: Option<String> = conn
.query_first(
r#"
SELECT
rc_title
FROM
recentchanges
WHERE
rc_namespace = 0
ORDER BY
rc_timestamp DESC
LIMIT
1"#,
)
.await?;
// Close the connections and pool (optional, this would happen
// automatically when the variables are dropped)
drop(conn);
pool.disconnect().await?;
// This unwrap is safe because we can assume that 1 row will always be returned
Ok(resp.unwrap())
}
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![index])
.attach(Template::fairing())
}