use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use linkify::{LinkFinder, LinkKind};
use url::Url;
use crate::service;
use crate::database;
mod urls;
mod visits;
mod analytics;
pub(crate) use self::urls::*;
pub(crate) use self::visits::*;
pub(crate) use self::analytics::*;
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ProcessResponse {
id: uuid::fmt::Simple,
replacement: String,
}
pub(crate) async fn process(
State(state): State<service::State>,
content: String,
) -> Result<Json<ProcessResponse>, StatusCode> {
let mut transaction = state.pool.begin()
.await
.map_err(|err| service::server_error(err, "Could not open transaction"))?;
let doc_id = database::document::persist(&content, &mut *transaction)
.await
.map_err(|err| service::server_error(err, "Could not persist document"))?;
let mut new_content = String::with_capacity(content.capacity());
let mut cur_index: u16 = 0;
let spans = LinkFinder::new()
.kinds(&[LinkKind::Url])
.spans(&content)
.collect::<Vec<_>>();
for span in spans {
if span.kind() != Some(&LinkKind::Url) {
new_content.push_str(span.as_str());
continue;
}
let url = Url::parse(span.as_str())
.map_err(|err| service::server_error(err, "Libraries do not agree on URL validity"))?;
let url_id = database::url::persist(&url, &doc_id, cur_index, &mut *transaction)
.await
.map_err(|err| service::server_error(err, "Could not persist URL"))?;
let new_url = state.settings.host_url
.join(&format!("/visit/{}", url_id.simple()))
.expect("host_url and `/visit/{url_id}` known to be valid");
new_content.push_str(new_url.as_str());
cur_index += 1
}
transaction.commit()
.await
.map_err(|err| service::server_error(err, "Could not commit transaction"))?;
Ok(Json(ProcessResponse {
id: doc_id.simple(),
replacement: new_content
}))
}